本文共 7888 字,大约阅读时间需要 26 分钟。
在上一篇《》中我们完成了对浏览器请求的解析,这一篇我们继续来实现响应浏览器的请求,同样的,我们还是先来看一下服务端响应给浏览器的数据格式
HTTP/1.1 200 OKServer: Apache-Coyote/1.1Accept-Ranges: bytesETag: W/"129-1456125361109"Last-Modified: Mon, 22 Feb 2016 07:16:01 GMTContent-Type: text/htmlContent-Length: 129Date: Mon, 22 Feb 2016 08:08:32 GMTtest this is test page.
只要我们响应的数据满足这个格式,浏览器就可以正常解析了,在解析之前还是先来做一些准备工作。
相应请求时可能会出现异常,所以我们先编写一个异常类,和请求的异常类类似。package com.gujin.server;/** * 响应异常 * * @author jianggujin * */public class HQResponseException extends RuntimeException{ private static final long serialVersionUID = 1L; public HQResponseException() { super(); } public HQResponseException(String message) { super(message); } public HQResponseException(String message, Throwable cause) { super(message, cause); } public HQResponseException(Throwable cause) { super(cause); }}
不管是请求还是响应都需要对字符串进行操作,所以将字符串操作的方法抽取出来形成一个字符串工具类。
package com.gujin.server.utils;/** * 字符串工具 * * @author jianggujin * */public class HQString{ /** * 判断字符串为空 * * @param s * @return */ public static boolean isEmpty(String s) { return s == null || s.length() == 0; } /** * 判断字符串是否非空 * * @param s * @return */ public static boolean isNotEmpty(String s) { return !isEmpty(s); } /** * 判断字符串是空白字符 * * @param s * @return */ public static boolean isBlack(String s) { return isEmpty(s) || s.matches("\\s*"); } /** * 判断字符串是非空白字符F * * @param s * @return */ public static boolean isNotBlack(String s) { return !isBlack(s); } /** * 截取字符串 * * @param s * @param flag * @return */ public static String subStringBefore(String s, String flag) { if (isEmpty(s) || isEmpty(flag)) { return s; } int index = s.indexOf(flag); if (index != -1) { return s.substring(0, index); } return flag; } /** * 截取字符串 * * @param s * @param flag * @return */ public static String subStringAfter(String s, String flag) { if (isEmpty(s) || isEmpty(flag)) { return s; } int index = s.indexOf(flag); if (index != -1) { return s.substring(index + flag.length()); } return flag; }}
好了,准备工作已经基本完成了,下面我们来编写响应类
package com.gujin.server;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.OutputStream;import java.net.Socket;import java.text.MessageFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Locale;import com.gujin.server.utils.HQString;/** * HTTP响应 * * @author jianggujin * */public class HQResponse{ /** 缓冲区大小 **/ private final int BUFSIZE = 512; /** 响应时间格式化 **/ private final String RESPONSE_DATE_TIME = "EEE, dd MMM yyyy HH:mm:ss 'GMT'"; /** HTTP版本 **/ private final String HTTP_VERSION = "HTTP/1.1"; /** 响应时间格式化 **/ private final SimpleDateFormat RESPONSE_DATE_FORMAT = new SimpleDateFormat( RESPONSE_DATE_TIME, Locale.US); /** 缓冲输出流 **/ private ByteArrayOutputStream bufferStream = null; /** Socket输出流 **/ private OutputStream stream = null; /** 响应码 **/ private int statusCode = 200; /** 内容类型 **/ private String contentType; /** * 构造方法 * * @param socket * @throws IOException */ public HQResponse(Socket socket) throws IOException { bufferStream = new ByteArrayOutputStream(BUFSIZE); stream = socket.getOutputStream(); } /** * 向客户端写数据 * * @param data * @throws IOException */ public void write(byte[] data) throws IOException { bufferStream.write(data); } /** * 向客户端写数据 * * @param data * @param start * @param len */ public void write(byte[] data, int start, int len) { bufferStream.write(data, start, len); } /** * 向客户端发送头信息 * * @throws IOException */ private void writeHeader() throws IOException { stream.write(MessageFormat.format("{0} {1} {2}\r\n", HTTP_VERSION, statusCode, "OK").getBytes()); stream.write(MessageFormat.format("Date: {0}\r\n", RESPONSE_DATE_FORMAT.format(new Date())).getBytes()); stream.write("Server: HQHttpServer 1.0".getBytes()); if (HQString.isNotEmpty(contentType)) { stream.write(MessageFormat .format("Content-Type: {0}\r\n", contentType).getBytes()); } stream.write(MessageFormat.format("Content-Length: {0}\r\n", bufferStream.size()).getBytes()); } /** * 实际响应 * * @throws IOException */ public void response() throws IOException { writeHeader(); // 换一行 stream.write("\r\n".getBytes()); bufferStream.writeTo(stream); bufferStream.flush(); stream.flush(); } /** * 获得内容类型 * * @return */ public String getContentType() { return contentType; } /** * 设置内容类型 * * @param contentType */ public void setContentType(String contentType) { this.contentType = contentType; }}
最后我们对上一篇博客中的HQHttpServer
类中的handleRequest
方法进行改造,将浏览器请求的头信息响应给浏览器,完成一次交互,完整代码如下:
package com.gujin.server;import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;import java.text.MessageFormat;import java.util.Iterator;import java.util.logging.Level;import com.gujin.server.utils.HQClose;/** * 服务端 * * @author jianggujin * */public class HQHttpServer implements HQHttpServerLog{ /** 端口号 **/ private int port = 80; /** 服务套接字 **/ private ServerSocket serverSocket = null; /** * 默认构造方法 */ public HQHttpServer() { } /** * 构造方法 * * @param port */ public HQHttpServer(int port) { this.port = port; } /** * 启动服务器 */ public synchronized void start() { try { serverSocket = new ServerSocket(port); LOG.info("server init success."); } catch (IOException e) { LOG.log(Level.SEVERE, e.getMessage(), e); } new Thread() { public void run() { while (!isStop()) { Socket socket; try { socket = serverSocket.accept(); handleRequest(socket); } catch (IOException e) { LOG.log(Level.SEVERE, e.getMessage(), e); } } }; }.start(); } /** * 处理请求 * * @param socket * @throws IOException */ public void handleRequest(Socket socket) throws IOException { HQRequest request = new HQRequest(socket); request.execute(); HQResponse response = new HQResponse(socket); response.setContentType("text/plain"); Iteratoriterator = request.getHeaderNames(); while (iterator.hasNext()) { String name = iterator.next(); response.write(MessageFormat.format("{0}: {1}", name, request.getHeader(name)).getBytes()); } response.response(); socket.close(); } /** * 是否停止 * * @return */ public boolean isStop() { return serverSocket == null || serverSocket.isClosed(); } /** * 停止服务器 */ public synchronized void stop() { if (!isStop()) { HQClose.safeClose(serverSocket); serverSocket = null; } } public static void main(String[] args) { new HQHttpServer().start(); }}
运行程序,在浏览器输入:http://127.0.0.1
,我们可以看到浏览器已经可以正常的接收服务端相应的数据了,页面显示内容如下:
ACCEPT-ENCODING: gzip,deflate,sdchHOST: 127.0.0.1USER-AGENT: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36CONNECTION: keep-aliveACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8ACCEPT-LANGUAGE: zh-CN,zh;q=0.8
打开浏览器的开发者工具观察服务端响应的数据信息
我们可以看到浏览器接收到的头信心与我们相应的数据是一致的。
到这里,一个最简单的WEB服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。