在之前的博客中,我们完成了WEB服务器的请求解析以及响应,达到了最基本的要求,我们都知道WEB服务器在处理请求的时候可以获得客户端提交的参数外,还可以获得Cookie信息,响应的时候也会对Cookie进行修改,当然了,Cookie我们可以通过读取或设置头信息来处理,但是这样会很麻烦,所以我们需要将Cookie的处理抽取出来,方便我们使用。另外,服务器在响应客户端请求时不仅仅包含200
状态码,还有可能是404
、500
等,所以我们需要对HQRequest
和HQResponse
进行修改并添加Cookie处理的相关类以及HTTP状态码枚举。
首先,我们将HTTP响应码抽取出来,形成一个枚举类,在枚举对象中包含了响应状态吗以及状态码描述,代码如下:
package com.gujin.server;
/**
* HTTP响应状态码
*
* @author jianggujin
*
*/
public enum HQHttpStatus
{
/** 客户端应当继续发送请求 **/
SC_100(100, "Continue"),
/** 服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求 **/
SC_101(101, "Switching Protocols"),
/** 由WebDAV(RFC 2518)扩展的状态码,代表处理将被继续执行 **/
SC_102(102, "Processing"),
/** 请求已成功,请求所希望的响应头或数据体将随此响应返回 **/
SC_200(200, "OK"),
/** 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立,且其 URI 已经随Location 头信息返回 **/
SC_201(201, "Created"),
/** 服务器已接受请求,但尚未处理 **/
SC_202(202, "Accepted"),
/** 服务器已成功处理了请求,但返回的实体头部元信息不是在原始服务器上有效的确定集合,而是来自本地或者第三方的拷贝 **/
SC_203(203, "Non-Authoritative Information"),
/** 服务器成功处理了请求,但不需要返回任何实体内容,并且希望返回更新了的元信息 **/
SC_204(204, "No Content"),
/** 服务器成功处理了请求,且没有返回任何内容 **/
SC_205(205, "Reset Content"),
/** 服务器已经成功处理了部分 GET 请求 **/
SC_206(206, "Partial Content"),
/** 由WebDAV(RFC 2518)扩展的状态码,代表之后的消息体将是一个XML消息,并且可能依照之前子请求数量的不同,包含一系列独立的响应代码 **/
SC_207(207, "Multi-Status"),
/** 被请求的资源有一系列可供选择的回馈信息,每个都有自己特定的地址和浏览器驱动的商议信息。用户或浏览器能够自行选择一个首选的地址进行重定向 **/
SC_300(300, "Multiple Choices"),
/** 被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一 **/
SC_301(301, "Moved Permanently"),
/** 请求的资源临时从不同的 URI响应请求 **/
SC_302(302, "Move temporarily"),
/** 对应当前请求的响应可以在另一个 URI 上被找到,而且客户端应当采用 GET 的方式访问那个资源 **/
SC_303(303, "See Other"),
/** 如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码 **/
SC_304(304, "Not Modified"),
/**
* 被请求的资源必须通过指定的代理才能被访问。Location 域中将给出指定的代理所在的 URI
* 信息,接收者需要重复发送一个单独的请求,通过这个代理才能访问相应资源
**/
SC_305(305, "Use Proxy"),
/** 在最新版的规范中,306状态码已经不再被使用 **/
SC_306(306, "Switch Proxy"),
/** 请求的资源临时从不同的URI 响应请求 **/
SC_307(307, "Temporary Redirect"),
/** 1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。2、请求参数有误 **/
SC_400(400, "Bad Request"),
/** 当前请求需要用户验证 **/
SC_401(401, "Unauthorized"),
/** 该状态码是为了将来可能的需求而预留的 **/
SC_402(402, "Payment Required"),
/** 服务器已经理解请求,但是拒绝执行它 **/
SC_403(403, "Forbidden"),
/** 请求失败,请求所希望得到的资源未被在服务器上发现 **/
SC_404(404, "Not Found"),
/** 请求行中指定的请求方法不能被用于请求相应的资源 **/
SC_405(405, "Method Not Allowed"),
/** 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体 **/
SC_406(406, "Not Acceptable"),
/** 与401响应类似,只不过客户端必须在代理服务器上进行身份验证 **/
SC_407(407, "Proxy Authentication Required"),
/** 请求超时 **/
SC_408(408, "Request Timeout"),
/** 由于和被请求的资源的当前状态之间存在冲突,请求无法完成 **/
SC_409(409, "Conflict"),
/** 被请求的资源在服务器上已经不再可用,而且没有任何已知的转发地址 **/
SC_410(410, "Gone"),
/** 服务器拒绝在没有定义 Content-Length 头的情况下接受请求 **/
SC_411(411, "Length Required"),
/** 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个 **/
SC_412(412, "Precondition Failed"),
/** 服务器拒绝处理当前请求,因为该请求提交的实体数据大小超过了服务器愿意或者能够处理的范围 **/
SC_413(413, "Request Entity Too Large"),
/**
* 请求的URI 长度超过了服务器能够解释的长度,因此服务器拒绝对该请求提供服务。这比较少见,通常的情况包括:
* 本应使用POST方法的表单提交变成了GET方法,导致查询字符串(Query String)过长。 重定向URI “黑洞”,例如每次重定向把旧的
* URI 作为新的 URI 的一部分,导致在若干次重定向后 URI 超长
**/
SC_414(414, "Request-URI Too Long"),
/** 对于当前请求的方法和所请求的资源,请求中提交的实体并不是服务器中所支持的格式,因此请求被拒绝 **/
SC_415(415, "Unsupported Media Type"),
/**
* 如果请求中包含了 Range 请求头,并且 Range 中指定的任何数据范围都与当前资源的可用范围不重合,同时请求中又没有定义 If-Range
* 请求头,那么服务器就应当返回416状态码
**/
SC_416(416, "Requested Range Not Satisfiable"),
/**
* 在请求头 Expect 中指定的预期内容无法被服务器满足,或者这个服务器是一个代理服务器,它有明显的证据证明在当前路由的下一个节点上,Expect
* 的内容无法被满足
**/
SC_417(417, "Expectation Failed"),
/** 从当前客户端所在的IP地址到服务器的连接数超过了服务器许可的最大范围 **/
SC_421(421, "There are too many connections from your internet address"),
/** 请求格式正确,但是由于含有语义错误,无法响应 **/
SC_422(422, "Unprocessable Entity"),
/** 当前资源被锁定 **/
SC_423(423, "Locked"),
/** 由于之前的某个请求发生的错误,导致当前请求失败,例如 PROPPATCH **/
SC_424(424, "Failed Dependency"),
/** 在WebDav Advanced Collections 草案中定义,但是未出现在《WebDAV 顺序集协议》(RFC 3658)中 **/
SC_425(425, "Unordered Collection"),
/** 客户端应当切换到TLS/1.0 **/
SC_426(426, "Upgrade Required"),
/** 由微软扩展,代表请求应当在执行完适当的操作后进行重试 **/
SC_449(449, "Retry With"),
/** 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理 **/
SC_500(500, "Internal Server Error"),
/** 服务器不支持当前请求所需要的某个功能 **/
SC_501(501, "Not Implemented"),
/** 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应 **/
SC_502(502, "Bad Gateway"),
/** 由于临时的服务器维护或者过载,服务器当前无法处理请求 **/
SC_503(503, "Service Unavailable"),
/**
* 作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)
* 收到响应
**/
SC_504(504, "Gateway Timeout"),
/** 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本 **/
SC_505(505, "HTTP Version Not Supported"),
/**
* 由《透明内容协商协议》(RFC
* 2295)扩展,代表服务器存在内部配置错误:被请求的协商变元资源被配置为在透明内容协商中使用自己,因此在一个协商处理中不是一个合适的重点
**/
SC_506(506, "Variant Also Negotiates"),
/** 服务器无法存储完成请求所必须的内容 **/
SC_507(507, "Insufficient Storage"),
/** 服务器达到带宽限制 **/
SC_509(509, "Bandwidth Limit Exceeded"),
/** 获取资源所需要的策略并没有没满足 **/
SC_510(510, "Not Extended"),
/** 源站没有返回响应头部,只返回实体内容 **/
SC_600(600, "Unparseable Response Headers");
/** 状态码 **/
private int code;
/** 状态描述 **/
private String descript;
/**
* 构造方法
*
* @param code
* @param descript
*/
private HQHttpStatus(int code, String descript)
{
this.code = code;
this.descript = descript;
}
/**
* 获得状态码
*
* @return
*/
public int getCode()
{
return code;
}
/**
* 获得描述
*
* @return
*/
public String getDescript()
{
return descript;
}
}
然后我们来编写Cookie的处理类,在编写处理类之前,我们先抽象出Cookie的实体类:
package com.gujin.server;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Cookie
*
* @author jianggujin
*
*/
public class HQCookie
{
/** Cookie名称 **/
private String name;
/** Cookie值 **/
private String value;
/** Cookie路径 **/
private String path = "/";
/** Cookie域名 **/
private String domain;
/** Cookie失效时间 **/
private String expires;
/** Cookie有效时间 **/
private int maxAge;
/** Cookie说明 **/
private String comment;
/** 使用Cookie有效时间标志 **/
private boolean useMaxAge = false;
/**
* 构造方法
*
* @param name
* @param value
*/
public HQCookie(String name, String value)
{
this.name = name;
this.value = value;
}
/**
* 构造方法
*/
public HQCookie()
{
}
/**
* 设置Cookie名称
*
* @param name
*/
public void setName(String name)
{
this.name = name;
}
/**
* 设置Cookie值
*
* @param value
*/
public void setValue(String value)
{
this.value = value;
}
/**
* 设置Cookie路径
*
* @param path
*/
public void setPath(String path)
{
this.path = path;
}
/**
* 设置Cookie域名
*
* @param domain
*/
public void setDomain(String domain)
{
this.domain = domain;
}
/**
* 设置Cookie失效时间
*
* @param expires
*/
public void setExpires(Date expires)
{
this.expires = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
Locale.US).format(expires);
}
/**
* 设置Cookie有效时间
*
* @param maxAge
*/
public void setMaxAge(int maxAge)
{
useMaxAge = true;
this.maxAge = maxAge;
}
/**
* 设置Cookie说明
*
* @param comment
*/
public void setComment(String comment)
{
this.comment = comment;
}
public String getName()
{
return name;
}
public String getValue()
{
return value;
}
public String getPath()
{
return path;
}
public String getDomain()
{
return domain;
}
public String getExpires()
{
return expires;
}
public int getMaxAge()
{
return maxAge;
}
public String getComment()
{
return comment;
}
public String getCookie()
{
if (name == null)
{
throw new IllegalArgumentException("the cookie name is null.");
}
StringBuilder cookie = new StringBuilder();
cookie.append(name).append("=").append(value);
cookie.append("; ").append("path").append("=").append(path);
if (comment != null)
{
cookie.append("; ").append("comment").append("=").append(comment);
}
if (expires != null)
{
cookie.append("; ").append("expires").append("=").append(expires);
}
if (useMaxAge)
{
cookie.append("; ").append("max-age").append("=").append(maxAge);
}
if (domain != null)
{
cookie.append("; ").append("domain").append("=").append(domain);
}
return cookie.toString();
}
@Override
public String toString()
{
return getCookie();
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (this.name != null)
{
if (obj instanceof HQCookie)
{
HQCookie cookie = (HQCookie) obj;
return this.name.equals(cookie.name);
}
if (obj instanceof String)
{
return this.name.equals(obj);
}
}
return super.equals(obj);
}
}
Cookie信息是放在请求头Cookie
中的,所以我们需要对请求头中的Cookie
值进行解析:
package com.gujin.server;
import java.util.ArrayList;
import java.util.Iterator;
/**
* Cookie处理器
*
* @author jianggujin
*
*/
public class HQCookieHandler implements Iterable<HQCookie>, Cloneable
{
/** Cookie集合 **/
private ArrayList<HQCookie> cookies = new ArrayList<HQCookie>();
/**
* 构造方法
*
* @param request
*/
public HQCookieHandler(HQRequest request)
{
String raw = request.getHeader("Cookie");
if (raw != null)
{
String[] tokens = raw.split(";");
for (String token : tokens)
{
String[] data = token.trim().split("=");
if (data.length == 2)
{
this.cookies.add(new HQCookie(data[0], data[1]));
}
}
}
}
/**
* 迭代器
*/
@Override
public Iterator<HQCookie> iterator()
{
return this.cookies.iterator();
}
/**
* 获得Cookie值
*
* @param name
* @return
*/
public HQCookie get(String name)
{
Iterator<HQCookie> iterator = iterator();
while (iterator.hasNext())
{
HQCookie cookie = iterator.next();
if (cookie.equals(name))
{
return cookie;
}
}
return null;
}
/**
* 添加Cookie
*
* @param name
* @param value
*/
public void addCookie(String name, String value)
{
addCookie(new HQCookie(name, value));
}
/**
* 添加Cookie
*
* @param cookie
*/
public void addCookie(HQCookie cookie)
{
this.cookies.add(cookie);
}
/**
* 设置Cookie
*
* @param name
* @param value
*/
public void setCookie(String name, String value)
{
setCookie(new HQCookie(name, value));
}
/**
* 设置Cookie
*
* @param cookie
*/
public void setCookie(HQCookie cookie)
{
Iterator<HQCookie> iterator = iterator();
while (iterator.hasNext())
{
HQCookie tmp = iterator.next();
if (tmp.equals(cookie))
{
iterator.remove();
}
}
this.cookies.add(cookie);
}
/**
* 删除Cookie
*
* @param name
*/
public void delete(String name)
{
HQCookie cookie = new HQCookie(name, "-delete-");
cookie.setMaxAge(-30);
setCookie(cookie);
}
@Override
public HQCookieHandler clone() throws CloneNotSupportedException
{
return (HQCookieHandler) super.clone();
}
}
这样,对Cookie的解析就完成了,然后我们需要对HQRequest
进行修改以完成对Cookie的解析工作,修改后的HQRequest
如下:
package com.gujin.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.gujin.server.utils.HQString;
/**
* HTTP请求
*
* @author jianggujin
*
*/
public class HQRequest
{
/** 缓冲区大小 **/
private final int BUFSIZE = 512;
/** 字节输出流 **/
private ByteArrayOutputStream outputStream = null;
/** Socket输入流 **/
private InputStream stream = null;
/** 请求方法 **/
private HQMethod method;
/** 请求路径 **/
private String url;
/** 查询字符串 **/
private String queryString;
/** HTTP版本 **/
private String version;
/** 请求头 **/
private Map<String, List<String>> headers = null;
/** 请求参数 **/
private Map<String, List<String>> params = null;
/** 请求编码 **/
private String charset = null;
/** 套接字 **/
private Socket socket = null;
private HQCookieHandler cookieHandler = null;
/**
* 构造方法
*
* @param socket
* @throws IOException
*/
public HQRequest(Socket socket) throws IOException
{
this.socket = socket;
outputStream = new ByteArrayOutputStream(512);
headers = new HashMap<String, List<String>>();
params = new HashMap<String, List<String>>();
this.stream = socket.getInputStream();
}
/**
* 执行解析
*
* @throws IOException
*/
public void execute() throws IOException
{
parseFirstLine();
parseHeaders();
parseBody();
cookieHandler = new HQCookieHandler(this);
}
/**
* 解析请求第一行,请求方法、请求地址、HTTP版本
*
* @throws IOException
*/
private void parseFirstLine() throws IOException
{
String line = readLine();
try
{
int index = line.indexOf(' ');
int lastIndex = line.lastIndexOf(' ');
this.method = HQMethod.valueOf(line.substring(0, index).toUpperCase());
String fullUrl = line.substring(index + 1, lastIndex);
int tmp = fullUrl.indexOf('?');
if (tmp > 0)
{
this.url = fullUrl.substring(0, tmp);
this.queryString = fullUrl.substring(tmp + 1);
dealParamString(queryString);
}
else
{
this.url = fullUrl;
}
this.version = line.substring(lastIndex + 1).toUpperCase();
}
catch (Exception e)
{
e.printStackTrace();
throw new HQRequestException("Request format unqualified.");
}
}
/**
* 解析请求头
*/
private void parseHeaders() throws IOException
{
String line = null;
while ((line = readLine()) != null)
{
// 分隔符位置
if (line.length() == 0)
{
break;
}
addHeader(line);
}
}
/**
* 添加请求头信息
*
* @param line
*/
private void addHeader(String line)
{
int index = line.indexOf(": ");
String name = line.substring(0, index).toUpperCase();
String value = line.substring(index + 2);
List<String> list = this.headers.get(name);
if (list == null)
{
list = new ArrayList<String>(1);
this.headers.put(name, list);
}
list.add(value);
}
/**
* 处理查询参数
*
* @param queryString
*/
private void dealParamString(String queryString)
{
if (HQString.isNotEmpty(queryString))
{
String[] params = queryString.split("&");
if (params != null)
{
for (String param : params)
{
addParameter(param);
}
}
}
}
/**
* 添加参数
*
* @param name
* @param value
*/
private void addParameter(String line)
{
int index = line.indexOf("=");
String name = line.substring(0, index);
String value = line.substring(index + 1);
List<String> values = params.get(name);
if (values == null)
{
values = new ArrayList<String>();
params.put(name, values);
}
values.add(value);
}
/**
* 获得请求参数
*
* @param name
* @return
*/
public String getParameter(String name)
{
List<String> values = params.get(name);
if (values != null && !values.isEmpty())
{
if (HQString.isEmpty(charset))
{
return values.get(0);
}
else
{
try
{
return URLDecoder.decode(values.get(0), charset);
}
catch (UnsupportedEncodingException e)
{
}
}
}
return null;
}
/**
* 获得请求参数名称
*
* @return
*/
public Iterator<String> getParameterNames()
{
return params.keySet().iterator();
}
/**
* 获得请求参数对应值
*
* @param name
* @return
*/
public String[] getParameterValues(String name)
{
List<String> values = params.get(name);
if (values != null)
{
if (HQString.isEmpty(charset))
{
return values.toArray(new String[0]);
}
else
{
int len = values.size();
String[] v = new String[len];
try
{
for (int i = 0; i < len; i++)
{
v[i] = URLDecoder.decode(values.get(i), charset);
}
return v;
}
catch (UnsupportedEncodingException e)
{
}
}
}
return null;
}
/**
* 解析请求体
*
* @throws IOException
*/
private void parseBody() throws IOException
{
if (HQMethod.POST == method)
{
int len = getContentLength() - outputStream.size();
if (len > 0)
{
byte[] buffer = new byte[BUFSIZE];
long total = 0;
int readLen = -1;
do
{
long left = len - total;
if (left <= 0)
{
break;
}
if (left >= BUFSIZE)
{
readLen = stream.read(buffer);
}
else
{
readLen = stream.read(buffer, 0, (int) left);
}
if (readLen < 0)
{
break;
}
outputStream.write(buffer, 0, readLen);
total += readLen;
} while (total < len);
outputStream.flush();
if (HQString.isEmpty(charset))
{
dealParamString(outputStream.toString());
}
else
{
dealParamString(outputStream.toString(charset));
}
}
}
}
/**
* 从输入流中读取一行
*
* @return
* @throws IOException
*/
private String readLine() throws IOException
{
String line = null;
int i = -1;
while ((i = stream.read()) != -1)
{
if (i == '\r')
{
outputStream.flush();
line = outputStream.toString();
outputStream.reset();
i = stream.read();
if (i != '\n')
{
outputStream.write(i);
}
break;
}
else if (i == '\n')
{
outputStream.flush();
line = outputStream.toString();
outputStream.reset();
break;
}
outputStream.write(i);
}
return line;
}
/**
* 获得请求方法
*
* @return
*/
public HQMethod getMethod()
{
return method;
}
/**
* 获得请求路径
*
* @return
*/
public String getUrl()
{
return url;
}
/**
* 获得HTTP版本
*
* @return
*/
public String getVersion()
{
return version;
}
/**
* 获得请求头信息
*
* @param name
* @return
*/
public String getHeader(String name)
{
List<String> values = headers.get(name.toUpperCase());
if (values != null && !values.isEmpty())
{
return values.get(0);
}
return null;
}
/**
* 获得请求头名称
*
* @return
*/
public Iterator<String> getHeaderNames()
{
return headers.keySet().iterator();
}
/**
* 获得请求头对应值
*
* @param name
* @return
*/
public List<String> getHeaderValues(String name)
{
return headers.get(name);
}
/**
* 获得内容长度
*
* @return
*/
public int getContentLength()
{
String contentLength = getHeader("Content-Length");
if (contentLength != null && contentLength.matches("\\d+"))
{
return Integer.parseInt(contentLength);
}
return -1;
}
/**
* 获得内容类型
*
* @return
*/
public String getContentType()
{
return getHeader("Content-Type");
}
/**
* 获得编码
*
* @return
*/
public String getCharset()
{
return charset;
}
/**
* 设置编码
*
* @param charset
*/
public void setCharset(String charset)
{
this.charset = charset;
}
/**
* 获得查询字符串
*
* @return
*/
public String getQueryString()
{
return queryString;
}
/**
* 获得远程主机地址
*
* @return
*/
public String getRemoteAddr()
{
return socket.getInetAddress().getHostAddress();
}
/**
* 获得远程主机名
*
* @return
*/
public String getRemoteHost()
{
return socket.getInetAddress().getHostName();
}
/**
* 获得远程主机端口
*
* @return
*/
public int getRemotePort()
{
return socket.getPort();
}
/**
* 获得本地主机名称
*
* @return
*/
public String getLocalName()
{
return socket.getLocalAddress().getHostName();
}
/**
* 获得本地主机地址
*
* @return
*/
public String getLocalAddr()
{
return socket.getLocalAddress().getHostAddress();
}
/**
* 获得本地主机端口
*
* @return
*/
public int getLocalPort()
{
return socket.getLocalPort();
}
/**
* 获得输入流
*
* @return
*/
public InputStream getInputStream()
{
return new ByteArrayInputStream(outputStream.toByteArray());
}
/**
* 获得Cookie解析执行器
*
* @return
*/
public HQCookieHandler getCookieHandler()
{
return cookieHandler;
}
/**
* 获得Cookie
*
* @param name
* @return
*/
public HQCookie getCookie(String name)
{
return this.cookieHandler.get(name);
}
/**
* 获得所有Cookie
*
* @return
*/
public Iterator<HQCookie> getCookies()
{
return cookieHandler.iterator();
}
}
同样的,我们需要完成对HQResponse
的改造,改造后的代码如下:
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.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
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 HQHttpStatus status = HQHttpStatus.SC_200;
/** 内容类型 **/
private String contentType;
/** chunked模式 **/
private boolean isChunked = false;
/** 已经响应消息 **/
private boolean alreadyResponse = false;
/** 响应头 **/
private Map<String, List<String>> headers = null;
/** Cookie **/
private HQCookieHandler cookieHandler = null;
/**
* 构造方法
*
* @param socket
* @throws IOException
*/
public HQResponse(Socket socket) throws IOException
{
bufferStream = new ByteArrayOutputStream(BUFSIZE);
headers = new HashMap<String, List<String>>();
stream = socket.getOutputStream();
}
/**
* 向客户端写数据
*
* @param data
* @throws IOException
*/
public void write(byte[] data) throws IOException
{
write(data, 0, data.length);
}
/**
* 向客户端写数据
*
* @param data
* @param start
* @param len
* @throws IOException
*/
public void write(byte[] data, int start, int len) throws IOException
{
if (isChunked)
{
if (!alreadyResponse)
{
writeHeader();
alreadyResponse = true;
}
stream.write(MessageFormat.format("{0}\r\n", Integer.toHexString(len))
.getBytes());
stream.write(data, start, len);
stream.write("\r\n".getBytes());
}
else
{
bufferStream.write(data, start, len);
}
}
/**
* 向客户端发送头信息
*
* @throws IOException
*/
private void writeHeader() throws IOException
{
stream.write(MessageFormat.format("{0} {1} {2}\r\n", HTTP_VERSION,
status.getCode(), status.getDescript()).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());
}
if (isChunked)
{
stream.write("Transfer-Encoding: chunked\r\n".getBytes());
}
else
{
stream.write(MessageFormat.format("Content-Length: {0}\r\n",
bufferStream.size()).getBytes());
}
// 输出头
Iterator<Entry<String, List<String>>> iterator = headers.entrySet()
.iterator();
while (iterator.hasNext())
{
Entry<String, List<String>> entry = iterator.next();
for (String value : entry.getValue())
{
stream.write(MessageFormat.format("{0}: {1}\r\n", entry.getKey(),
value).getBytes());
}
}
Iterator<HQCookie> cookieIterator = cookieHandler.iterator();
while (cookieIterator.hasNext())
{
HQCookie cookie = cookieIterator.next();
stream.write(MessageFormat.format("Set-Cookie: {0}\r\n",
cookie.toString()).getBytes());
}
// 换一行
stream.write("\r\n".getBytes());
}
/**
* 实际响应
*
* @throws IOException
*/
public void response() throws IOException
{
if (isChunked)
{
// 发送结束标志
stream.write("0\r\n\r\n".getBytes());
}
else
{
writeHeader();
bufferStream.writeTo(stream);
bufferStream.flush();
}
stream.flush();
}
/**
* 获得内容类型
*
* @return
*/
public String getContentType()
{
return contentType;
}
/**
* 设置内容类型
*
* @param contentType
*/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
/**
* 发送错误码
*
* @param code
*/
public void sendError(HQHttpStatus status)
{
if (isChunked && alreadyResponse)
{
throw new HQResponseException("already response.");
}
this.status = status;
}
/**
* 是否为chunked模式
*
* @return
*/
public boolean isChunked()
{
return isChunked;
}
/**
* 设置chunked模式
*
* @param isChunked
*/
public void setChunked(boolean isChunked)
{
if (isChunked && alreadyResponse)
{
throw new HQResponseException("already response.");
}
this.isChunked = isChunked;
}
/**
* 添加响应头
*
* @param name
* @param value
*/
public void addHeader(String name, String value)
{
if (HQString.isEmpty(name) || HQString.isEmpty(value))
{
return;
}
List<String> list = this.headers.get(name);
if (list == null)
{
list = new ArrayList<String>(1);
this.headers.put(name, list);
}
list.add(value);
}
/**
* 设置响应头
*/
public void setHeader(String name, String value)
{
if (HQString.isEmpty(name) || HQString.isEmpty(value))
{
return;
}
List<String> list = this.headers.get(name);
if (list == null)
{
list = new ArrayList<String>(1);
this.headers.put(name, list);
}
else
{
list.clear();
}
list.add(value);
}
/**
* 添加Cookie
*
* @param name
* @param value
*/
public void addCookie(String name, String value)
{
addCookie(new HQCookie(name, value));
}
/**
* 添加Cookie
*
* @param cookie
*/
public void addCookie(HQCookie cookie)
{
cookieHandler.addCookie(cookie);
}
/**
* 设置Cookie
*
* @param name
* @param value
*/
public void setCookie(String name, String value)
{
setCookie(new HQCookie(name, value));
}
/**
* 设置Cookie
*
* @param cookie
*/
public void setCookie(HQCookie cookie)
{
cookieHandler.setCookie(cookie);
}
/**
* 删除Cookie
*
* @param name
*/
public void delete(String name)
{
HQCookie cookie = new HQCookie(name, "-delete-");
cookie.setMaxAge(-30);
setCookie(cookie);
}
/**
* 获得Cookie解析执行器
*
* @return
*/
public HQCookieHandler getCookieHandler()
{
return cookieHandler;
}
/**
* 设置Cookie解析执行器
*
* @param cookieHandler
*/
public void setCookieHandler(HQCookieHandler cookieHandler)
{
this.cookieHandler = cookieHandler;
}
}
细心的小伙伴可以发现,在HQResponse
中不仅添加了对Cookie的支持,还添加了chunked模式的返回,有兴趣的朋友可以了解一下chunked模式返回数据的格式。
完成了请求与响应的改造后,想要其正常工作,我们还需要对处理请求的方法进行修改,即修改HQHttpServer
,修改后代码如下:
package com.gujin.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import com.gujin.server.utils.HQClose;
/**
* 服务端
*
* @author jianggujin
*
*/
public abstract 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();
LOG.info("accept client...");
handleRequest(socket);
}
catch (Exception e)
{
LOG.log(Level.SEVERE, e.getMessage(), e);
}
}
};
}.start();
}
/**
* 处理请求
*
* @param socket
* @throws IOException
*/
protected void handleRequest(Socket socket) throws IOException
{
HQRequest request = new HQRequest(socket);
request.execute();
HQResponse response = new HQResponse(socket);
try
{
response.setCookieHandler(request.getCookieHandler().clone());
}
catch (CloneNotSupportedException e)
{
LOG.log(Level.SEVERE, e.getMessage(), e);
}
handleRequest(request, response);
response.response();
HQClose.safeClose(socket);
}
/**
* 处理请求
*
* @param request
* @param response
* @throws IOException
*/
public abstract void handleRequest(HQRequest request, HQResponse response)
throws IOException;
/**
* 是否停止
*
* @return
*/
public boolean isStop()
{
return serverSocket == null || serverSocket.isClosed();
}
/**
* 停止服务器
*/
public synchronized void stop()
{
if (!isStop())
{
HQClose.safeClose(serverSocket);
serverSocket = null;
}
}
}
最后,我们通过测试代码来对我们改造后的服务器进行测试。
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Iterator;
import com.gujin.server.HQCookie;
import com.gujin.server.HQHttpServer;
import com.gujin.server.HQRequest;
import com.gujin.server.HQResponse;
/**
* 服务端测试类
*
* @author jianggujin
*
*/
public class HttpServerTest extends HQHttpServer
{
@Override
public void handleRequest(HQRequest request, HQResponse response)
throws IOException
{
response.setContentType("text/html");
response.setCookie("HttpServerTest", "HttpServerTestValue");
response.setChunked(true);
Iterator<HQCookie> cookies = request.getCookies();
while (cookies.hasNext())
{
LOG.info(cookies.next().toString());
}
response.write![这里写图片描述](http://img.blog.csdn.net/20160225144236077)("<html><head><meta charset=\"UTF-8\"></head><body><ul>"
.getBytes());
response.write("<html>".getBytes());
Iterator<String> iterator = request.getHeaderNames();
while (iterator.hasNext())
{
String name = iterator.next();
response.write(MessageFormat.format("<li>{0}: {1}</li>", name,
request.getHeader(name)).getBytes());
}
response.write("</ul></body></html>".getBytes());
}
public static void main(String[] args)
{
new HttpServerTest().start();
}
}
运行程序,浏览器访问:http://127.0.0.1
,刷新一次后,我们可以看到控制台输出: