博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JAVA简易WEB服务器(三)
阅读量:6883 次
发布时间:2019-06-27

本文共 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 GMT
testthis 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"); Iterator
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服务器已经完成了与浏览器的一次完成整的交互,接下来我们要继续对其优化。

你可能感兴趣的文章
AHSC DAY2总结
查看>>
java.lang.SecurityException: class "javax.servlet.FilterRegistration"(spark下maven)
查看>>
[Vue CLI 3] 配置解析之 css.extract
查看>>
Linux——信息采集(三)dmitry、路由跟踪命令tracerouter
查看>>
提取ipa里面的资源图片 png
查看>>
wxpython ItemContainer
查看>>
工作中 Oracle 常用数据字典集锦
查看>>
SFB 项目经验-12-为某上市企业的Skype for Business购买Godday证书
查看>>
[C#基础知识]专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
查看>>
大数据虚拟化零起点-2基础运维第一步-环境规划和准备
查看>>
Skype for Business Server 2015-04-前端服务器-3-安装-管理工具
查看>>
docker入门指南(转载)
查看>>
Java RGB数组图像合成 ImageCombining (整理)
查看>>
第八届河南省赛F.Distribution(水题)
查看>>
Android 下拉刷新上拉载入效果功能
查看>>
第九篇 :微信公众平台开发实战Java版之如何实现自定义分享内容
查看>>
SDL2源码分析1:初始化(SDL_Init())
查看>>
swift通过摄像头读取每一帧的图片,并且做识别做人脸识别
查看>>
你对自己的定位是什么,就能成为什么样的人(转)
查看>>
全文检索引擎Solr系列——整合中文分词组件IKAnalyzer
查看>>