JAVA简易WEB服务器(三)
在上一篇《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服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。
上一篇: Java中死锁的例子及其解决办法