在上一篇《JAVA简易WEB服务器(二)》中我们完成了对浏览器请求的解析,这一篇我们继续来实现响应浏览器的请求,同样的,我们还是先来看一下服务端响应给浏览器的数据格式
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Accept-Ranges: bytes
ETag: W/"129-1456125361109"
Last-Modified: Mon, 22 Feb 2016 07:16:01 GMT
Content-Type: text/html
Content-Length: 129
Date: Mon, 22 Feb 2016 08:08:32 GMT
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>test</title> </head> <body>this is test page. </body> </html>
只要我们响应的数据满足这个格式,浏览器就可以正常解析了,在解析之前还是先来做一些准备工作。
相应请求时可能会出现异常,所以我们先编写一个异常类,和请求的异常类类似。
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");
Iterator<String> iterator = 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服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。