httpclient 发送 post 请求: <[]>

HttpURLConnection 发送 post 请求: <[]>

javasocket 发送 post 请求: <[http://www.tuicool.com/articles/Rb2MVz]>

package com.qingyuan.httpclient;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 使用JavaSocket编写发送HTTP_POST请求的工具类
 * @see 与之类似的还有一个HttpClientUtil工具类
 * @see 地址为
 * @see 还有一个使用Java原生API编写发送HTTP_POST请求的工具类
 * @see 地址为
 * @create Apr 4, 2013 8:37:44 PM
 * @author 玄玉<;
 */
public class HTTPUtil {
  private HTTPUtil(){}
  
  public static void main(String[] args) throws Exception 
  {
	  Map<String, String> params = new HashMap<String, String>();
	  params.put("goodId", "goodId");
	  params.put("goodsDesc", "goodsDesc");
	  params.put("merUserId", "merUserId");
	  params.put("merExtend", "merExtend");
	  params.put("merReqSerial", "merReqSerial");
	  params.put("orderDate", new SimpleDateFormat("yyyyMMdd").format(new Date()));
	  params.put("merReqTime", new SimpleDateFormat("HHmmss").format(new Date()));
	  params.put("signMsg", "This is RequestParam sign");
	  Map<String, String> respMap = 
		  sendPostRequest("http://localhost:8080/HttpUrlConnection/servlet/LoginServlet", params, "UTF-8");
	  System.out.println("=============================================================================");
	  System.out.println("请求报文如下");
	  System.out.println(respMap.get("reqMsg"));
	  System.out.println("=============================================================================");
	  System.out.println("响应报文如下");
	  System.out.println(respMap.get("respMsg"));
	  System.out.println("=============================================================================");
	  System.out.println("响应十六进制如下");
	  System.out.println(respMap.get("respMsgHex"));
	  System.out.println("=============================================================================");
	}
  
  /**
   * 发送HTTP_POST请求
   * @see 本方法默认的连接超时和读取超时均为30秒
   * @see 请求参数含有中文时,亦可直接传入本方法中,本方法内部会自动根据reqCharset参数进行<code>URLEncoder.encode()</code>
   * @see 解码响应正文时,默认取响应头[Content-Type=text/html; charset=GBK]字符集,若无Content-Type,则使用UTF-8解码
   * @param reqURL     请求地址
   * @param reqParams  请求正文数据
   * @param reqCharset 请求报文的编码字符集(主要针对请求参数值含中文而言)
   * @return reqMsg-->HTTP请求完整报文,respMsg-->HTTP响应完整报文,respMsgHex-->HTTP响应的原始字节的十六进制表示
   */
  public static Map<String, String> sendPostRequest(String reqURL, Map<String, String> reqParams, String reqCharset) 
  {
    StringBuilder reqData = new StringBuilder();
    for (Map.Entry<String, String> entry : reqParams.entrySet()) 
    {
      try 
      { 
        reqData.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), reqCharset)).append("&");
      } 
      catch (UnsupportedEncodingException e) 
      {
        System.out.println("编码字符串[" + entry.getValue() + "]时发生异常:系统不支持该字符集[" + reqCharset + "]");
        reqData.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
      }
    }
    if (reqData.length() > 0) 
    {
      reqData.setLength(reqData.length() - 1); //删除最后一个&符号
    }
    
    return sendPostRequest(reqURL, reqData.toString(), reqCharset);
  }
  
  
  /**
   * 发送HTTP_POST请求
   * @see you can see {@link HTTPUtil#sendPostRequest(String, Map, String)}
   * @see 注意:若欲直接调用本方法,切记请求参数值含中文时,一定要对该参数值<code>URLEncoder.encode(value, reqCharset)</code>
   * @see 注意:这里只是对key=value中的'value'进行encode,而非'key='..encode完毕后,再组织成key=newValue传给本方法
   */
  public static Map<String, String> sendPostRequest(String reqURL, String reqData, String reqCharset)
  {
    Map<String, String> respMap = new HashMap<String, String>();
    OutputStream out = null; //写
    InputStream in = null;   //读
    Socket socket = null;    //客户机
    String respMsg = null;
    String respMsgHex = null;
    String respCharset = "UTF-8";
    StringBuilder reqMsg = new StringBuilder();
    try 
    {
      URL sendURL = new URL(reqURL);
      String host = sendURL.getHost();
      int port = sendURL.getPort()==-1 ? 80 : sendURL.getPort();
      /**
       * 创建Socket
       * @see ---------------------------------------------------------------------------------------------------
       * @see 通过有参构造方法创建Socket对象时,客户机就已经发出了网络连接请求,连接成功则返回Socket对象,反之抛IOException
       * @see 客户端在连接服务器时,也要进行通讯,客户端也需要分配一个端口,这个端口在客户端程序中不曾指定
       * @see 这时就由客户端操作系统自动分配一个空闲的端口,默认的是自动的连续分配
       * @see 如服务器端一直运行着,而客户端不停的重复运行,就会发现默认分配的端口是连续分配的
       * @see 即使客户端程序已经退出了,系统也没有立即重复使用先前的端口
       * @see socket = new Socket(host, port);
       * @see ---------------------------------------------------------------------------------------------------
       * @see 不过,可以通过下面的方式显式的设定客户端的IP和Port
       * @see socket = new Socket(host, port, InetAddress.getByName("127.0.0.1"), 8765);
       * @see ---------------------------------------------------------------------------------------------------
       * 
       * 核心代碼:
       * 1) 產生socket 對象:socket = new Socket();
       * 2) socket 對象獲取連接  out = socket.getOutputStream();
       * 3) 根據sock 獲取的連接流發送http post 請求 out.write(reqMsg.toString().getBytes());
       * 4) 獲取服務端的相應: in = socket.getInputStream();
       * 5) 合理處理IO 流: 開啟和關閉
       */
      socket = new Socket();
      /**
       * 设置Socket属性
       */
      //true表示关闭Socket的缓冲,立即发送数据..其默认值为false
      //若Socket的底层实现不支持TCP_NODELAY选项,则会抛出SocketException
      socket.setTcpNoDelay(true);
      //表示是否允许重用Socket所绑定的本地地址
      socket.setReuseAddress(true);
      //表示接收数据时的等待超时时间,单位毫秒..其默认值为0,表示会无限等待,永远不会超时
      //当通过Socket的输入流读数据时,如果还没有数据,就会等待
      //超时后会抛出SocketTimeoutException,且抛出该异常后Socket仍然是连接的,可以尝试再次读数据
      socket.setSoTimeout(30000);
      //表示当执行Socket.close()时,是否立即关闭底层的Socket
      //这里设置为当Socket关闭后,底层Socket延迟5秒后再关闭,而5秒后所有未发送完的剩余数据也会被丢弃
      //默认情况下,执行Socket.close()方法,该方法会立即返回,但底层的Socket实际上并不立即关闭
      //它会延迟一段时间,直到发送完所有剩余的数据,才会真正关闭Socket,断开连接
      //Tips:当程序通过输出流写数据时,仅仅表示程序向网络提交了一批数据,由网络负责输送到接收方
      //Tips:当程序关闭Socket,有可能这批数据还在网络上传输,还未到达接收方
      //Tips:这里所说的"未发送完的剩余数据"就是指这种还在网络上传输,未被接收方接收的数据
      socket.setSoLinger(true, 5);
      //表示发送数据的缓冲区的大小
      socket.setSendBufferSize(1024);
      //表示接收数据的缓冲区的大小
      socket.setReceiveBufferSize(1024);
      //表示对于长时间处于空闲状态(连接的两端没有互相传送数据)的Socket,是否要自动把它关闭,true为是
      //其默认值为false,表示TCP不会监视连接是否有效,不活动的客户端可能会永久存在下去,而不会注意到服务器已经崩溃
      socket.setKeepAlive(true);
      //表示是否支持发送一个字节的TCP紧急数据,socket.sendUrgentData(data)用于发送一个字节的TCP紧急数据
      //其默认为false,即接收方收到紧急数据时不作任何处理,直接将其丢弃..若用户希望发送紧急数据,则应设其为true
      //设为true后,接收方会把收到的紧急数据与普通数据放在同样的队列中
      socket.setOOBInline(true);
      //该方法用于设置服务类型,以下代码请求高可靠性和最小延迟传输服务(把0x04与0x10进行位或运算)
      //Socket类用4个整数表示服务类型
      //0x02:低成本(二进制的倒数第二位为1)
      //0x04:高可靠性(二进制的倒数第三位为1)
      //0x08:最高吞吐量(二进制的倒数第四位为1)
      //0x10:最小延迟(二进制的倒数第五位为1)
      socket.setTrafficClass(0x04 | 0x10);
      //该方法用于设定连接时间,延迟,带宽的相对重要性(该方法的三个参数表示网络传输数据的3项指标)
      //connectionTime--该参数表示用最少时间建立连接
      //latency---------该参数表示最小延迟
      //bandwidth-------该参数表示最高带宽
      //可以为这些参数赋予任意整数值,这些整数之间的相对大小就决定了相应参数的相对重要性
      //如这里设置的就是---最高带宽最重要,其次是最小连接时间,最后是最小延迟
      socket.setPerformancePreferences(2, 1, 3);
      
      /**
       * 连接服务端
       */
      //客户端的Socket构造方法请求与服务器连接时,可能要等待一段时间
      //默认的Socket构造方法会一直等待下去,直到连接成功,或者出现异常
      //若欲设定这个等待时间,就要像下面这样使用不带参数的Socket构造方法,单位是毫秒
      //若超过下面设置的30秒等待建立连接的超时时间,则会抛出SocketTimeoutException
      //注意:如果超时时间设为0,则表示永远不会超时
      socket.connect(new InetSocketAddress(host, port), 30000);
      
      /**
       * 构造HTTP请求报文
       */
      reqMsg.append("POST ").append(sendURL.getPath()).append(" HTTP/1.1\r\n");
      reqMsg.append("Cache-Control: no-cache\r\n");
      reqMsg.append("Pragma: no-cache\r\n");
      reqMsg.append("User-Agent: JavaSocket/").append(System.getProperty("java.version")).append("\r\n");
      reqMsg.append("Host: ").append(sendURL.getHost()).append("\r\n");
      reqMsg.append("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n");
      reqMsg.append("Connection: keep-alive\r\n");
      reqMsg.append("Content-Type: application/x-www-form-urlencoded; charset=").append(reqCharset).append("\r\n");
      reqMsg.append("Content-Length: ").append(reqData.getBytes().length).append("\r\n");
      reqMsg.append("\r\n");
      reqMsg.append(reqData);
      
      /**
       * 发送HTTP请求
       */
      out = socket.getOutputStream();
      //这里针对getBytes()补充一下:之所以没有在该方法中指明字符集(包括上面头信息组装Content-Length的时候)
      //是因为传进来的请求正文里面不会含中文,而非中文的英文字母符号等等,其getBytes()无论是否指明字符集,得到的都是内容一样的字节数组
      //所以更建议不要直接调用本方法,而是通过sendPostRequest(String, Map<String, String>, String)间接调用本方法
      //sendPostRequest(.., Map, ..)在调用本方法前,会自动对请求参数值进行URLEncoder(注意不包括key=value中的'key=')
      //而该方法的第三个参数reqCharset只是为了拼装HTTP请求头信息用的,目的是告诉服务器使用哪种字符集解码HTTP请求报文里面的中文信息
      out.write(reqMsg.toString().getBytes());
      
      /**
       * 接收HTTP响应
       */
      in = socket.getInputStream();
      //事实上就像JDK的API所述:Closing a ByteArrayOutputStream has no effect
      //查询ByteArrayOutputStream.close()的源码会发现,它没有做任何事情,所以其close()与否是无所谓的
      ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
      byte[] buffer = new byte[512];
      int len = -1;
      
      while((len=in.read(buffer)) != -1)
      {
        //将读取到的字节写到ByteArrayOutputStream中
        //所以最终ByteArrayOutputStream的字节数应该等于HTTP响应报文的整体长度,而大于HTTP响应正文的长度
        bytesOut.write(buffer, 0, len);
      }
      //响应的原始字节数组
      byte[] respBuffer = bytesOut.toByteArray();
      respMsgHex = formatToHexStringWithASCII(respBuffer);
     
      /**
       * 获取Content-Type中的charset值(Content-Type: text/html; charset=GBK)
       */
      int from = 0;
      int to = 0;
      for(int i=0; i<respBuffer.length; i++)
      {
        if((respBuffer[i]==99||respBuffer[i]==67) 
        		&& (respBuffer[i+1]==111||respBuffer[i+1]==79) 
        		&& (respBuffer[i+2]==110||respBuffer[i+2]==78) 
        		&& (respBuffer[i+3]==116||respBuffer[i+3]==84) 
        		&& (respBuffer[i+4]==101||respBuffer[i+4]==69) 
        		&& (respBuffer[i+5]==110||respBuffer[i+5]==78) 
        		&& (respBuffer[i+6]==116||respBuffer[i+6]==84) 
        		&& respBuffer[i+7]==45 
        		&& (respBuffer[i+8]==84||respBuffer[i+8]==116) 
        		&& (respBuffer[i+9]==121||respBuffer[i+9]==89) 
        		&& (respBuffer[i+10]==112||respBuffer[i+10]==80) 
        		&& (respBuffer[i+11]==101||respBuffer[i+11]==69))
        {
          from = i;
          //既然匹配到了Content-Type,那就一定不会匹配到我们想到的\r\n,所以就直接跳到下一次循环中喽..
          continue;
        }
        if(from>0 && to==0 && respBuffer[i]==13 && respBuffer[i+1]==10)
        {
          //一定要加to==0限制,因为可能存在Content-Type后面还有其它的头信息
          to = i;
          //既然得到了你想得到的,那就不要再循环啦,徒做无用功而已
          break;
        }
      }
      //解码HTTP响应头中的Content-Type
      byte[] headerByte = Arrays.copyOfRange(respBuffer, from, to);
      //HTTP响应头信息无中文,用啥解码都可以
      String contentType = new String(headerByte);
      //提取charset值
      if(contentType.toLowerCase().contains("charset"))
      {
        respCharset = contentType.substring(contentType.lastIndexOf("=") + 1);
      }
      /**
       * 解码HTTP响应的完整报文
       */
      respMsg = bytesOut.toString(respCharset);
    } 
    catch (Exception e) 
    {
      System.out.println("与[" + reqURL + "]通信遇到异常,堆栈信息如下");
      e.printStackTrace();
    } 
    finally 
    {
      if (null!=socket && socket.isConnected() && !socket.isClosed()) {
        try 
        {
          //此时socket的输出流和输入流也都会被关闭
          //值得注意的是:先后调用Socket的shutdownInput()和shutdownOutput()方法
          //值得注意的是:仅仅关闭了输入流和输出流,并不等价于调用Socket.close()方法
          //通信结束后,仍然要调用Socket.close()方法,因为只有该方法才会释放Socket占用的资源,如占用的本地端口等
          socket.close();
        } 
        catch (IOException e) 
        {
          System.out.println("关闭客户机Socket时发生异常,堆栈信息如下");
          e.printStackTrace();
        }
      }
    }
    respMap.put("reqMsg", reqMsg.toString());
    respMap.put("respMsg", respMsg);
    respMap.put("respMsgHex", respMsgHex);
    return respMap;
  }
  
  /**
   * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
   * @see 该方法会将字节数组中的所有字节均格式化为字符串
   * @see 使用说明详见<code>formatToHexStringWithASCII(byte[], int, int)</code>方法
   */
  private static String formatToHexStringWithASCII(byte[] data)
  {
    return formatToHexStringWithASCII(data, 0, data.length);
  }
  
  /**
   * 通过ASCII码将十进制的字节数组格式化为十六进制字符串
   * @see 该方法常用于字符串的十六进制打印,打印时左侧为十六进制数值,右侧为对应的字符串原文
   * @see 在构造右侧的字符串原文时,该方法内部使用的是平台的默认字符集,来解码byte[]数组
   * @see 该方法在将字节转为十六进制时,默认使用的是<code>java.util.Locale.getDefault()</code>
   * @see 详见String.format(String, Object...)方法和new String(byte[], int, int)构造方法
   * @param data   十进制的字节数组
   * @param offset 数组下标,标记从数组的第几个字节开始格式化输出
   * @param length 格式长度,其不得大于数组长度,否则抛出java.lang.ArrayIndexOutOfBoundsException
   * @return 格式化后的十六进制字符串
   */
  private static String formatToHexStringWithASCII(byte[] data, int offset, int length)
  {
    int end = offset + length;
    StringBuilder sb = new StringBuilder();
    StringBuilder sb2 = new StringBuilder();
    sb.append("\r\n------------------------------------------------------------------------");
    boolean chineseCutFlag = false;
    
    for(int i=offset; i<end; i+=16)
    {
      sb.append(String.format("\r\n%04X: ", i-offset)); //X或x表示将结果格式化为十六进制整数
      sb2.setLength(0);
      for(int j=i; j<i+16; j++)
      {
        if(j < end)
        {
          byte b = data[j];
          if(b >= 0)
          { //ENG ASCII
            sb.append(String.format("%02X ", b));
            if(b<32 || b>126)
            { //不可见字符
              sb2.append(" ");
            }
            else
            {
              sb2.append((char)b);
            }
          }
          else
          { //CHA ASCII
            if(j == i+15)
            { //汉字前半个字节
              sb.append(String.format("%02X ", data[j]));
              chineseCutFlag = true;
              String s = new String(data, j, 2);
              sb2.append(s);
            }
            else if(j == i&&chineseCutFlag)
            { //后半个字节
              sb.append(String.format("%02X ", data[j]));
              chineseCutFlag = false;
              String s = new String(data, j, 1);
              sb2.append(s);
            }
            else
            {
              sb.append(String.format("%02X %02X ", data[j], data[j + 1]));
              String s = new String(data, j, 2);
              sb2.append(s);
              j++;
            }
          }
        }else{
          sb.append("   ");
        }
      }
      sb.append("| ");
      sb.append(sb2.toString());
    }
    sb.append("\r\n------------------------------------------------------------------------");
    return sb.toString();
  }
  
}



<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
	http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <servlet>
    <description>This is the description of my J2EE component</description>
    <display-name>This is the display name of my J2EE component</display-name>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.qingyuan.httpclient.LoginServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/servlet/LoginServlet</url-pattern>
  </servlet-mapping>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>



package com.qingyuan.httpclient;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		this.doPost(request, response);

	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		String goodId = request.getParameter("goodId");
		String goodsDesc = request.getParameter("goodsDesc");
		String merUserId = request.getParameter("merUserId");
		String merReqSerial = request.getParameter("merReqSerial");
		String orderDate = request.getParameter("orderDate");
		String merReqTime = request.getParameter("merReqTime");

		response.setContentType("text/html;charset=utf-8");
		response.setCharacterEncoding("utf-8");
		PrintWriter out = response.getWriter();
		out.write("requestData>>>>>>>goodId="+goodId+";goodsDesc="+goodsDesc+
				";merUserId="+merUserId+";merReqSerial="+merReqSerial
				+";orderDate="+orderDate+";merReqTime"+merReqTime);

	}

}


=============================================================================
请求报文如下
POST /HttpUrlConnection/servlet/LoginServlet HTTP/1.1
Cache-Control: no-cache
Pragma: no-cache
User-Agent: JavaSocket/1.7.0_21
Host: localhost
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 170

merReqSerial=merReqSerial&orderDate=20160515&signMsg=This+is+RequestParam+sign&merExtend=merExtend&merReqTime=165152&merUserId=merUserId&goodId=goodId&goodsDesc=goodsDesc
=============================================================================
响应报文如下
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=utf-8
Content-Length: 133
Date: Sun, 15 May 2016 08:51:52 GMT

requestData>>>>>>>goodId=goodId;goodsDesc=goodsDesc;merUserId=merUserId;merReqSerial=merReqSerial;orderDate=20160515;merReqTime165152
=============================================================================
响应十六进制如下

------------------------------------------------------------------------
0000: 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D | HTTP/1.1 200 OK 
0010: 0A 53 65 72 76 65 72 3A 20 41 70 61 63 68 65 2D |  Server: Apache-
0020: 43 6F 79 6F 74 65 2F 31 2E 31 0D 0A 43 6F 6E 74 | Coyote/1.1  Cont
0030: 65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 2F 68 | ent-Type: text/h
0040: 74 6D 6C 3B 63 68 61 72 73 65 74 3D 75 74 66 2D | tml;charset=utf-
0050: 38 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E 67 74 | 8  Content-Lengt
0060: 68 3A 20 31 33 33 0D 0A 44 61 74 65 3A 20 53 75 | h: 133  Date: Su
0070: 6E 2C 20 31 35 20 4D 61 79 20 32 30 31 36 20 30 | n, 15 May 2016 0
0080: 38 3A 35 31 3A 35 32 20 47 4D 54 0D 0A 0D 0A 72 | 8:51:52 GMT    r
0090: 65 71 75 65 73 74 44 61 74 61 3E 3E 3E 3E 3E 3E | equestData>>>>>>
00A0: 3E 67 6F 6F 64 49 64 3D 67 6F 6F 64 49 64 3B 67 | >goodId=goodId;g
00B0: 6F 6F 64 73 44 65 73 63 3D 67 6F 6F 64 73 44 65 | oodsDesc=goodsDe
00C0: 73 63 3B 6D 65 72 55 73 65 72 49 64 3D 6D 65 72 | sc;merUserId=mer
00D0: 55 73 65 72 49 64 3B 6D 65 72 52 65 71 53 65 72 | UserId;merReqSer
00E0: 69 61 6C 3D 6D 65 72 52 65 71 53 65 72 69 61 6C | ial=merReqSerial
00F0: 3B 6F 72 64 65 72 44 61 74 65 3D 32 30 31 36 30 | ;orderDate=20160
0100: 35 31 35 3B 6D 65 72 52 65 71 54 69 6D 65 31 36 | 515;merReqTime16
0110: 35 31 35 32                                     | 5152
------------------------------------------------------------------------
=============================================================================