Java Socket Http

用Java  Socket 实现发送HTTP请求和读取响应。

public class LearnHttp {
	private static final byte CR = '\r';
	private static final byte LF = '\n';
	private static final byte[] CRLF = {CR, LF};

	public static void main(String[] args) throws UnknownHostException, IOException {
		new LearnHttp().testHttp();
	}
	
	public void testHttp() throws UnknownHostException, IOException {
		String host = "www.baidu.com";
		Socket socket = new Socket(host, 80);
		
		OutputStream out = socket.getOutputStream();
		InputStream in = socket.getInputStream();
		
		// 在同一个TCP连接里发送多个HTTP请求
		for(int i = 0; i < 2; i++) {
			writeRequest(out, host);
			readResponse(in);
			System.out.println("\n\n\n");
		}
	}
	
	private void writeRequest(OutputStream out, String host) throws IOException {
		// 请求行
		out.write("GET /index.html HTTP/1.1".getBytes());
		out.write(CRLF);		// 请求头的每一行都是以CRLF结尾的
		
		// 请求头
		out.write(("Host: " + host).getBytes()); // 此请求头必须
		out.write(CRLF);

		out.write(CRLF);		// 单独的一行CRLF表示请求头的结束

		// 可选的请求体。GET方法没有请求体
		
		out.flush();
	}

	private void readResponse(InputStream in) throws IOException {
		// 读取状态行
		String statusLine = readStatusLine(in);
		System.out.println("statusLine :" + statusLine);
		
		// 消息报头
		Map<String, String> headers = readHeaders(in);
		
		int contentLength = Integer.valueOf(headers.get("Content-Length"));
		
		// 可选的响应正文
		byte[] body = readResponseBody(in, contentLength);
		
		String charset = headers.get("Content-Type");
		if(charset.matches(".+;charset=.+")) {
			charset = charset.split(";")[1].split("=")[1];
		} else {
			charset = "ISO-8859-1";		// 默认编码
		}
		
		System.out.println("content:\n" + new String(body, charset));
	}
	
	private byte[] readResponseBody(InputStream in, int contentLength) throws IOException {
		
		ByteArrayOutputStream buff = new ByteArrayOutputStream(contentLength);
		
		int b;
		int count = 0;
		while(count++ < contentLength) {
			b = in.read();
			buff.write(b);
		}
		
		return buff.toByteArray();
	}
	
	private Map<String, String> readHeaders(InputStream in) throws IOException {
		Map<String, String> headers = new HashMap<String, String>();
		
		String line;
		
		while(!("".equals(line = readLine(in)))) {
			String[] nv = line.split(": ");		// 头部字段的名值都是以(冒号+空格)分隔的
			headers.put(nv[0], nv[1]);
		}
		
		return headers;
	}
	
	private String readStatusLine(InputStream in) throws IOException {
		return readLine(in);
	}
	
	/**
	 * 读取以CRLF分隔的一行,返回结果不包含CRLF
	 */
	private String readLine(InputStream in) throws IOException {
		int b;
		
		ByteArrayOutputStream buff = new ByteArrayOutputStream();
		while((b = in.read()) != CR) {
			buff.write(b);
		}
		
		in.read();		// 读取 LF
		
		String line = buff.toString();
		
		return line;
	}
	
}

值得记下的教训:

InputStream.read()返回-1表示流的结束,即流被关闭了。在这次实现同一个Socket发送多个HTTP请求的过程中,一开始总是以为一个响应报文的结束是在InputStream.read返回-1时,导致第一次读取响应时总是很久才结束,第二次的请求虽然发出去了,但响应总是空的。

读取第一次请求的响应要很久是因为,这是在等待服务器关闭连接,百度的基本上是60秒。第二次是空的是因为,请求虽然发出去了,但服务器写响应的流已经被关闭,所以也读不到响应。

导致这个问题的根本原因在于没有记住:TCP是面向流的,并不知道传输的消息的开始和结束,这是由上层的协议去控制的,这里是HTTP协议。

猜你喜欢

转载自wen866595.iteye.com/blog/1168658
今日推荐