HTTP协议可以说是WEB项目中最常用的协议了,项目开发中一般使用Httpclient进行HTTP接口的请求,Httpclient封装了HTTP协议的细节,如果不用Httpclient如何进行HTTP请求呢,下面是一个简单的实现HTTP协议的小程序:

主要类SimpleHttpGet:

package com.zws.http;

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.SocketAddress;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
 * 
 * @author wensh.zhu 2017-03-11
  请求报文格式:
	GET /greeting.do?name=jack HTTP/1.1\r\n
	Host: localhost:8011\r\n
	Connection: keep-alive\r\n
	Cache-Control: max-age=0\r\n
	Upgrade-Insecure-Requests: 1\r\n
	User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\r\n
	Accept: text/html,application/xhtml+xml,application/xml;q=0.9,p_w_picpath/webp,*此处为斜线(/)*;q=0.8\r\n
	Accept-Encoding: gzip, deflate, sdch, br\r\n
	Accept-Language: zh-CN,zh;q=0.8\r\n
	\r\n
   
  响应报文格式:
  一次性响应:
 	HTTP/1.1 200 OK\r\n
	Server: Apache-Coyote/1.1\r\n
	Set-Cookie: JSESSIONID=54B11BE01CD7983726618546CF4521E1; Path=/Newer/; HttpOnly\r\n
	Content-Type: text/html;charset=UTF-8\r\n
	Content-Length: 4\r\n						//报文正文长度
	Date: Sat, 11 Mar 2017 15:16:10 GMT\r\n
	\r\n										//报头结束
	abcd										//正文
	
      分块响应:
	HTTP/1.1 200 OK\r\n
	Content-Type: application/json;charset=UTF-8\r\n
	Transfer-Encoding: chunked\r\n						//表示分块响应
	Date: Sat, 11 Mar 2017 14:19:25 GMT\r\n
	\r\n     			//报头结束
	8\r\n				//十六进制长度
	abcdefgh\r\n		//正文 
	2\r\n
	ab\r\n
	0\r\n				//结束
 */
public class SimpleHttpGet {
	
	private int connectTimeout = 2000;	//链接超时时间
	private int readTimeout = 5000;		//读超时时间
	private String host;
	private int port = 80;
	private static String DEF_CHARSET = "UTF-8";
	private String resourcePath = "/";//资源路径
	private String reqLine;
	private Map<String, String> headers = new HashMap<String, String>();

	public SimpleHttpGet(String uri) {
		initRequestHeader();
		parseRequestLine(uri);
	}
	
	public SimpleHttpGet(String uri, int connectTimeout, int readTimeOut) {
		initRequestHeader();
		parseRequestLine(uri);
		this.connectTimeout = connectTimeout;
		this.readTimeout = readTimeOut;
	}
	/**
	 * 解析出IP地址、端口以及请求资源
	 * @param uri
	 * @throws UnsupportedEncodingException 
	 */
	private void parseRequestLine(String uri) {
		String url = uri;
		if (url == null || url.length() == 0) 
			throw new NullPointerException("uri can not be null");
		if (!url.startsWith("http"))
			url = "http://" + uri;
		String[] parts = url.split("//");
		
		String mainPart = parts[1];
		int ipFlag = mainPart.indexOf("/");
		if (ipFlag != -1) {
			String ipPort = mainPart.substring(0, ipFlag);
			
			String[] ipParts = ipPort.split(":");
			if (ipParts.length > 1) {
				host = ipParts[0];
				String portStr = ipParts[1];
				if (portStr != null && portStr.length() > 0)
					port = Integer.parseInt(portStr);
			} else {
				host = ipPort;
			}
			
			String resourcePart = mainPart.substring(ipFlag);
			
			resourcePath = resourcePart;
			
		} else {
			host = mainPart;
		}
		
		String hostVal = host;
		if (port != 80) hostVal += ":" + port;
		headers.put("Host", hostVal);

		reqLine = "GET " + resourcePath + " HTTP/1.1\r\n";
	}

	/**
	 * 初始化请求头
	 */
	private void initRequestHeader() {
		headers.put("Connection", "keep-alive");
		headers.put("Upgrade-Insecure-Requests", "1");
		headers.put("User-Agent", "Java client");
		headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,p_w_picpath/webp,*/*;q=0.8");
		headers.put("Accept-Encoding", "gzip");
		headers.put("Accept-Language", "zh-CN,zh");
		headers.put("Content-Type", "text/html;charset=utf-8");
	}
	
	public void setHeader(String key, String value) {
		headers.put(key, value);
	}
	
	public RespMsg req() {
		RespMsg msg = null;
		Socket socket = null;
		OutputStream out = null;
		InputStream in = null;
		try {
			SocketAddress endpoint = new InetSocketAddress(host, port);
			socket = new Socket();
			socket.connect(endpoint, connectTimeout);
			socket.setSoTimeout(readTimeout);

			out = socket.getOutputStream();
			write(out);
			
			in = socket.getInputStream();
			msg = read(in);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null)
					out.close();
				if (in != null)
					in.close();
				if (socket != null) 
					socket.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
			
		}
		return msg;
	}
	
	private void write(OutputStream out) throws IOException {
		String reqBody = reqLine;
		Iterator<String> itor = headers.keySet().iterator();
		while (itor.hasNext()) {
			String key = itor.next();
			String val = headers.get(key);
			String header = key + ":" + val + "\r\n";
			reqBody += header;
		}
		
		reqBody += "\r\n";
		System.out.println(reqBody);
		out.write(reqBody.getBytes());
	}
	
	private RespMsg read(InputStream in) throws IOException {
		RespMsg respMsg = new RespMsg();
		byte[] heads = HttpStreamReader.readHeaders(in);
		String headStr = new String(heads);
		String[] lines = headStr.split("\r\n");
		RespHeader resp = new RespHeader();
		if (lines.length > 0) 
			resp.setRespLine(lines[0]);
		
		for (int i = 1; i < lines.length; i++) 
			resp.addHeader(lines[i]);
		
		String body = null;
		if (resp.isChunked()) {
			body = readChunked(in);
		} else {
			int bodyLen = resp.getContentLenth();
			byte[] bodyBts = new byte[bodyLen];
			in.read(bodyBts);
			
			body = new String(bodyBts, DEF_CHARSET);
		}
		respMsg.setRespBody(body);
		String respLine = resp.getRespLine();
		respMsg.setRespCodeMsg(respLine);
		
		return respMsg;
	}
	/**
	 * 分块读取
	 * @param in
	 * @return
	 * @throws IOException
	 */
	private static String readChunked(InputStream in) throws IOException {
		String content = "";
		String lenStr = "0";
		while (!(lenStr = new String(HttpStreamReader.readLine(in))).equals("0")) {
			int len = Integer.valueOf(lenStr.toUpperCase(),16);//长度16进制表示
			byte[] cnt = new byte[len];
			in.read(cnt);
			content += new String(cnt, "UTF-8");
			in.skip(2);
		}
		
		return content;
	}

}

三个辅助类:

HttpStreamReader类:

package com.zws.http;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
 * 
 * @author wensh.zhu 2017-03-11
 * 
 */
public class HttpStreamReader {
	/**
	 * 行结束标识,\r\n(13,10)
	 */
	public static final byte[] LINE_END = {13, 10};
	/**
	 * 响应头结束标识,\r\n\r\n(13,10,13,10)
	 */
	public static final byte[] ALL_END = {13, 10, 13, 10};

	public static byte[] getBytes(InputStream in) throws IOException {
		byte[] buffer = new byte[1024];
		int len = 0;
		int ind = 0;
		while ((len = in.read(buffer, ind, buffer.length - ind)) > 0){
			if (len == buffer.length - ind) {//full 
				int l = buffer.length;
				byte[] newBuffer = new byte[l * 2];
				System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
				buffer = newBuffer;
				ind = l;
			} else {
				ind += len;
			}
		}
		byte[] result = new byte[ind];
		System.arraycopy(buffer, 0, result, 0, ind);
		return result;
	}
	
	public static byte[] readHeaders(InputStream in) throws IOException {
		return read(in, ALL_END);
	}
	
	public static byte[] readLine(InputStream in) throws IOException {
		return read(in, LINE_END);
	}
	
	public static byte[] read(InputStream in, byte[] endFlag) throws IOException {
		byte[] buffer = new byte[1024];
		int ind = 0;
		int bt = 0;
		while ((bt = in.read()) != -1){ 
			buffer[ind] = (byte)bt;
			if (isTailEqual(buffer, ind, endFlag))
				break;
			ind ++;
		}
		int newLen = ind + 1 - endFlag.length;
		byte[] result = new byte[newLen];
		System.arraycopy(buffer, 0, result, 0, newLen);
		return result;
	}
	
	/**
	 * bts的后ends.length个字节是否和ends相等
	 * @param bts
	 * @param ends
	 * @return
	 */
	public static boolean isTailEqual(byte[] bts, int endIndex, byte[] ends){
		int btsLen = endIndex + 1;
		int endLen = ends.length;
		
		if (btsLen < endLen) return false;
		
		int tailFrom = btsLen - endLen;
		int tailTo = btsLen;
		
		byte[] tail = Arrays.copyOfRange(bts, tailFrom, tailTo);
		
		for (int i = 0; i < endLen; i++) 
			if (tail[i] != ends[i]) 
				return false;
		
		return true;
	}
}

RespHeader类:

package com.zws.http;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
 * 
 * @author wensh.zhu 2017-03-11
 * 
 */
public class RespHeader {

	private String respLine;
	private Map<String, String> headers = new HashMap<String, String>();
	
	public void addHeader(String head) {
		if (head == null || head.length() < 1)
			return;
		String[] nameVal = head.split(":");
		String name = nameVal[0], val = null;
		if (nameVal.length > 1)
			val = nameVal[1].replaceFirst(" ", "");
		headers.put(name, val);
	}
	
	public String getHeader(String header) {
		return headers.get(header);
	}
	
	public int getContentLenth() {
		int len = 0;
		String lenStr = headers.get("Content-Length");
		if (lenStr == null || lenStr.length() < 1)
			return len;
		try {
			len = Integer.parseInt(lenStr);
		} catch (NumberFormatException e) {
			e.printStackTrace();
		}
		
		return len;
	}
	
	public String getSessionId() {
		String sessionId = null;
		String val = headers.get("Set-Cookie");
		if (val != null && val.length() > 0) {
			String[] arrary = val.split(";");
			String sessStr = arrary[0];
			if (sessStr != null && sessStr.length() > 1) {
				arrary = sessStr.split("=");
				if (arrary.length > 1)
					sessionId = arrary[1];
			}
		}
		
		return sessionId;
	}
	
	/**
	 * 是否是分块响应
	 * @return
	 */
	public boolean isChunked() {
		String val = headers.get("Transfer-Encoding");
		if (val == null || val.length() < 1) 
			return false;

		return "chunked".equals(val);
	}
	
	public String getRespLine() {
		return respLine;
	}
	public void setRespLine(String respLine) {
		this.respLine = respLine;
	}
	public Map<String, String> getHeaders() {
		return headers;
	}
	public void setHeaders(Map<String, String> headers) {
		this.headers = headers;
	}
	
	
	@Override
	public String toString() {
		String str = this.respLine + "\r\n";
		Set<String> keys = this.headers.keySet();
		for (String key : keys) {
			str += (key + ":" + this.headers.get(key) + "\r\n");
		}
		return str;
	}
	
}

RespMsg类:

package com.zws.http;
/**
 * 
 * @author wensh.zhu 2017-03-11
 * 
 */
public class RespMsg {

	private String respCodeMsg;
	private String respBody;
	
	public String getRespCodeMsg() {
		return respCodeMsg;
	}
	public void setRespCodeMsg(String respCodeMsg) {
		this.respCodeMsg = respCodeMsg;
	}
	
	public String getRespBody() {
		return respBody;
	}
	public void setRespBody(String respBody) {
		this.respBody = respBody;
	}
	
	@Override
	public String toString() {
		return respCodeMsg + "\n" + respBody;
	}
}

测试类:

package com.zws.http;

public class Example {

	public static void main(String[] args) {
		String uri = "http://localhost:8011/greeting";
		SimpleHttpGet get = new SimpleHttpGet(uri);
		RespMsg msg = get.req();
		String respCodeMsg = msg.getRespCodeMsg();
		String respBody = msg.getRespBody();
		
		System.out.println(respCodeMsg);
		System.out.println(respBody);
	}
}