点击关注公众号,实用技术文章及时了解

来源:binhao.blog.csdn.net/article/details/112059406

摘要

web开发一直是行业热门技术,而要做web程序就离不开http服务器,现在主流的http服务器用的最广的如tomcat,apache。还有商用版本的各式各样的http服务器,而再行业类各种微服务,各种web开发技术层出不穷,都是基于这些服务器上的架构上的使用,并没有从本质上提高服务器运行的效率,笔者在研究http服务的过程中,就花了一早上来写了这样一个http服务器展示http服务器工作的流程。

一、什么是http服务器

HTTP服务器一般指网站服务器,是指驻留于因特网上某种类型计算机的程序,可以处理浏览器等Web客户端的请求并返回相应响应,也可以放置网站文件,让全世界浏览;可以放置数据文件,让全世界下载。1目前最主流的三个Web服务器有Tomcat、Apache、 Nginx 。

http服务器用来干什么:

如下图所示,网络通过一个如tomcat一样的http服务器来获取一台计算机上的资源。计算机上的资源可以是计算机能提供的任何服务(包括文件,存储,计算能力等等)。

下图是tomcat中一个http请求的传递过程你,该图在tomcat官网可以找到。

一个请求到响应主要经过了tomcat的Engine->Host->Pipeline->执行servece方法响应http请求。

二、如何自己简单实现一个http服务器

一个简单的实现思路如下,最简单的过程通俗的说就是,请求进来,请求处理器将响应的结果生产出来,再有服务器发出响应。推荐:Java进阶资源

思路上一定是很简单的,实现一个良好可靠的http服务器确实一件很难的事。有很多复制的问题需要解决,如下:

  • 什么样的请求能进来或者说是有效的?(第一层请求拦截)

  • 请求的解析和响应结果的生成?(需要精通http协议)

  • io模型如何选择,怎样充分利用发挥服务器性能?(涉及多线程,复杂的io模型)

  • 可插拔要怎么实现?如tomcat可以通过一个servlet让开发人员进行处理。

  • …等等,实现一个http服务器绝对是一件很困难的事,所有网络应用层都是基于这个向上设计的。

三、自己实现的http服务器

说干就干,今早花了一个小时写了一个简单的demo来展示,先看下做出来的效果是什么样的。

先在我项目配置的文件夹下放两张图片

通过浏览器加文件名的方式来访问试试结果:

两张图片现在就可以通过网络来浏览了,怎么样,很神奇对吧,接下来就看下代码是怎么实现的?

四、Http服务器实现(Java)

HttpServer类:具体的server实现,并且将解析方法也在该类下:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Properties;/*** Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/1/1* ++++ ______                           ______             ______* +++/     /|                         /     /|           /     /|* +/_____/  |                       /_____/  |         /_____/  |* |     |   |                      |     |   |        |     |   |* |     |   |                      |     |   |________|     |   |* |     |   |                      |     |  /         |     |   |* |     |   |                      |     |/___________|     |   |* |     |   |___________________   |     |____________|     |   |* |     |  /                  / |  |     |   |        |     |   |* |     |/ _________________/  /   |     |  /         |     |  /* |_________________________|/b    |_____|/           |_____|/*/public class HttpServer {public static final String RESOURCE_PATH = "D:/image";public static void main(String[] args) throws IOException {ServerSocket server = new ServerSocket(80);while (true){Socket client = server.accept();InputStream inputStream = client.getInputStream();String s = handleNewClientMessage(inputStream);Request request = Request.parse(s);System.out.println(request);//假设请求的都是文件String uri = request.getUri();File file = new File(RESOURCE_PATH + uri);FileInputStream fileInputStream = new FileInputStream(file);FileChannel channel = fileInputStream.getChannel();ByteBuffer buffer = ByteBuffer.allocate((int) file.length());channel.read(buffer);buffer.rewind();Response response = new Response();response.setTopRow("HTTP/1.1 200 OK");response.getHeaders().put("Content-Type",readProperties(getType(request.getUri())));response.getHeaders().put("Content-Length",String.valueOf(file.length()));response.getHeaders().put("Server","httpserver-lbh");response.setBody(buffer.array());response.setOutputStream(client.getOutputStream());response.setClient(client);System.out.println(response);System.out.println(client.isClosed());//响应客户端response.service();inputStream.close();client.close();}}private static String handleNewClientMessage(InputStream inputStream) throws IOException {byte[] bytes = new byte[1024];int read = inputStream.read(bytes);StringBuilder buffer = new StringBuilder();if (read != -1) {for (byte b:bytes) {buffer.append((char)b);}}return buffer.toString();}/*** 我个人放mimetype文件类型的文件,需要单独修改*/private static final String MIME_PROPERTIES_LOCATION ="D:\\individual\\springcloudDemo\\src\\main\\java\\contentype.properties";public static String readProperties(String key) {System.out.println(System.getProperty("user.dir"));Properties prop = new Properties();try {prop.load(new FileInputStream(MIME_PROPERTIES_LOCATION));return prop.get(key).toString();} catch (IOException e) {e.printStackTrace();}return null;}public static String getType(String uri) {System.out.println("uri:" + uri);String stringDot = ".";String dot = "\\.";String[] fileName;if (uri.contains(stringDot)) {fileName = uri.split(dot);return fileName[fileName.length - 1];}return null;}}

Request类,请求进来的形式:

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;/*** Copyright(c)lbhbinhao@163.com* @author liubinhao* @date 2021/1/1* ++++ ______                           ______             ______* +++/     /|                         /     /|           /     /|* +/_____/  |                       /_____/  |         /_____/  |* |     |   |                      |     |   |        |     |   |* |     |   |                      |     |   |________|     |   |* |     |   |                      |     |  /         |     |   |* |     |   |                      |     |/___________|     |   |* |     |   |___________________   |     |____________|     |   |* |     |  /                  / |  |     |   |        |     |   |* |     |/ _________________/  /   |     |  /         |     |  /* |_________________________|/b    |_____|/           |_____|/*/public class Request {private String method;private String uri;private String version;private byte[] body;private Map<String, String> headers;public String getMethod() {return method;}public String getUri() {return uri;}public String getVersion() {return version;}public byte[] getBody() {return body;}public Map<String, String> getHeaders() {return headers;}public void setMethod(String method) {this.method = method;}public void setUri(String uri) {this.uri = uri;}public void setVersion(String version) {this.version = version;}public void setHeaders(Map<String, String> headers) {this.headers = headers;}public void setBody(byte[] body) {this.body = body;}@Overridepublic String toString() {return "Request{" +"method='" + method + '\'' +", uri='" + uri + '\'' +", version='" + version + '\'' +", body=" + Arrays.toString(body) +", headers=" + headers +'}';}private static final String CONTENT_SEPARATE_SIGN = "\r\n\r\n";private static final String LINE_SEPARATE_SIGN = "\r\n";private static final String SPACE_SEPARATE_SIGN = "\\s+";private static final String KV_SEPARATE_SIGN = ":";//**********************************解析请求*************************************private static void parseTopReq(Request req, String[] topReq){req.setMethod (topReq[0]);req.setUri    (topReq[1]);req.setVersion(topReq[2]);}private static void parseHeaders(Request req, String[] headerStr){HashMap<String, String> headers = new HashMap<>(12);for (String s : headerStr) {String[] keyValue = s.split(KV_SEPARATE_SIGN);headers.put(keyValue[0], keyValue[1]);}req.setHeaders(headers);}public static Request parse(String reqStr) {Request req = new Request();String[] httpInfo = reqStr.split(CONTENT_SEPARATE_SIGN);if (httpInfo.length==0){return null;}if (httpInfo.length > 1){req.setBody(httpInfo[1].getBytes());}String[] content  = httpInfo[0].split(LINE_SEPARATE_SIGN);// http first row of a http requestString[] firstRow = content[0].split(SPACE_SEPARATE_SIGN);// http / get demo_path.typeparseTopReq(req, firstRow);parseHeaders(req, Arrays.copyOfRange(content,1,content.length));return req;}
}

Response类,具体响应的结果:

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;/*** Copyright..@lbhbinhao@163.com* Author:liubinhao* Date:2020/12/28* ++++ ______                           ______             ______* +++/     /|                         /     /|           /     /|* +/_____/  |                       /_____/  |         /_____/  |* |     |   |                      |     |   |        |     |   |* |     |   |                      |     |   |________|     |   |* |     |   |                      |     |  /         |     |   |* |     |   |                      |     |/___________|     |   |* |     |   |___________________   |     |____________|     |   |* |     |  /                  / |  |     |   |        |     |   |* |     |/ _________________/  /   |     |  /         |     |  /* |_________________________|/b    |_____|/           |_____|/*/public class Response{private String topRow;private Map<String, String> headers = new HashMap<>(12);private byte[] body;private int responseCode;private OutputStream outputStream;public String getTopRow() {return topRow;}public void setTopRow(String topRow) {this.topRow = topRow;}public Map<String, String> getHeaders() {return headers;}public void setHeaders(Map<String, String> headers) {this.headers = headers;}public byte[] getBody() {return body;}public void setBody(byte[] body) {this.body = body;}public OutputStream getOutputStream() {return outputStream;}public void setOutputStream(OutputStream outputStream) {this.outputStream = outputStream;}public int getResponseCode() {return responseCode;}public void setResponseCode(int responseCode) {this.responseCode = responseCode;}public static final byte[] LINE_SEPARATE_SIGN_BYTES    = "\r\n".getBytes();public static final byte[] KV_SEPARATE_SIGN_BYTES      = ":".getBytes();private Socket client;public Socket getClient() {return client;}public void setClient(Socket client) {this.client = client;}//    如果头数据里不包含Content-Length: x 类型为Transfer-Encoding:
//    chunked 说明响应数据的长度不固定,结束符已"\r\n0\r\n"这5个字节为结束符public void service() throws IOException {outputStream.write(this.getTopRow().getBytes());outputStream.write(LINE_SEPARATE_SIGN_BYTES);for (Map.Entry<String, String> entry : this.getHeaders().entrySet()){outputStream.write(entry.getKey().getBytes());outputStream.write(KV_SEPARATE_SIGN_BYTES);outputStream.write(entry.getValue().getBytes());outputStream.write(LINE_SEPARATE_SIGN_BYTES);}outputStream.write(LINE_SEPARATE_SIGN_BYTES);outputStream.write(this.getBody());outputStream.flush();}@Overridepublic String toString() {return "Response{" +"topRow='" + topRow + '\'' +", headers=" + headers +
//                ", body=" + Arrays.toString(body) +", responseCode=" + responseCode +", client=" + client +'}';}
}

mimetype文件,用来判断请求的类型,由于文件太大,只复制一部分,格式都一样:

contentype.properties

#mdb=application/x-mdb
mfp=application/x-shockwave-flash
mht=message/rfc822
mhtml=message/rfc822
mi=application/x-mi
mid=audio/mid
midi=audio/mid
mil=application/x-mil
mml=text/xml
mnd=audio/x-musicnet-download
mns=audio/x-musicnet-stream
mocha=application/x-javascript
movie=video/x-sgi-movie
mp1=audio/mp1
mp2=audio/mp2
mp2v=video/mpeg
mp3=audio/mp3
mp4=video/mp4
mpa=video/x-mpg
mpd=application/vnd
#ms-project
mpe=video/x-mpeg
mpeg=video/mpg
mpg=video/mpg
mpga=audio/rn-mpeg
mpp=application/vnd
#ms-project
mps=video/x-mpeg
mpt=application/vnd

五、总结

实现一个http服务什么是核心呢,就在名字里,就是http协议,http协议本质上就是一个基于文本解析表示特定的语义规则,只要读懂了这些规则就能够理解一个http服务器。

但是要开发一个http服务器这个是远远不够的,还需要高超的编程技艺,个人上述只是一个小demo,看上去好些很简单的样子。在个人github上有一个更加完整的实现,基于nio和多线程,效率更高,实现的更加完善,并且支持断点续传的方式

github地址:

https://github.com/haobinliu/app-server/tree/server-x-test/server-x/src/com/lbh

欢迎一键三连

推荐:

主流Java进阶技术(学习资料分享)

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

手写一个HTTP图片资源服务器,太容易了叭!相关推荐

  1. vue前端用服务器上路径的图片展示_5分钟教你用nodeJS手写一个mock数据服务器

    对于前端开发者而言,javascript正扮演着越来越重要的地位,它不仅能为浏览器端赋能,在web服务器方面也有很大的价值(我们可以用nodeJS来写服务端代码,启动web服务器),因此本文所要描述的 ...

  2. 用 Node.js 手写一个 DNS 服务器

    DNS 是实现域名到 IP 转换的网络协议,当访问网页的时候,浏览器首先会通过 DNS 协议把域名转换为 IP,然后再向这个 IP 发送 HTTP 请求. DNS 是我们整天在用的协议,不知道大家是否 ...

  3. 【Linux服务器开发系列】手写一个用户态网络协议栈,瞬间提升你网络功底丨netmap/dpdk的实现

    手写一个用户态网络协议栈,瞬间提升你网络功底 1. 网卡基础架构 2. netmap/dpdk的实现 3. 网络协议栈实战 [Linux服务器开发系列]手写一个用户态网络协议栈,瞬间提升你网络功底丨n ...

  4. 手写一个原神祈愿分析工具

    手写一个原神祈愿分析工具 之前一直通过游创工坊来进行祈愿抽卡数据分析,但是广告太多,而且担心auth_key泄露,于是自己花了一天时间动手实现了个数据分析工具,数据永久保存在本地,没有信息泄露风险,话 ...

  5. 用Java手写一个微型下载资源网站

    文章目录 手写一个微型下载资源网站[Java实现用户注册.登陆.下载功能] 一.技术栈 二.流程分析图 注册 登陆 下载 三.案例实现效果 首页 注册 登陆 下载网主页 壁纸下载 书籍下载 影视下载 ...

  6. 硬核!手写一个优先队列

    文章收录在首发公众号:bigsai 期待你的到访! 前言 事情还要从一个故事讲起: 对于上面那只可爱的小狗狗不会,本篇即为该教程,首先,我要告诉这只可爱的小狗狗,这种问题你要使用的数据结构为优先队列, ...

  7. @cacheable 设置过期时间_缓存面试三连击——聊聊Redis过期策略?内存淘汰机制?再手写一个LRU 吧!...

    大家好,今天我和大家想聊一聊有关redis的过期策略的话题. 听到这里你也许会觉得:"我去,我只是个日常搬砖的,这种偏底层的知识点,我需要care吗?" 话虽如此·,但是兄die, ...

  8. tensorflow+python flask进行手写识别_python+flask搭建CNN在线识别手写中文网站!简直太屌了!...

    原标题:python+flask搭建CNN在线识别手写中文网站!简直太屌了! 使用python+flask搭建的一个网站,然后从网页的写字板上获取鼠标手写的汉字经过转码后传回后台,并经过图片裁剪处理之 ...

  9. 手写一个获取验证码的接口,超级简单

    手写一个获取验证码的接口,超级简单,觉得有用就试试吧,话不多说代码附上 private static final int VERIFY_CODE_HEIGHT = 25; //验证码高度private ...

最新文章

  1. 利用Powershell SSH-Session 工具管理 linux或网络设备
  2. 施一公:大学必须不计成败、不论得失地为探索者提供宽容和支持的环境
  3. ●BZOJ 2669 [cqoi2012]局部极小值
  4. C/C++ 输出整数带正负号
  5. LeetCode 45跳跃游戏46全排列
  6. org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert val
  7. nodejs内存溢出
  8. Linux命令TOP TEN
  9. 数据结构与算法6—树
  10. GMF学习系列(二) 一些知识点(续2)
  11. 40、使用javassit操作运行时字节码文件
  12. 什么是命名空间 为什么C++需要使用命名空间
  13. 多个 VUE 前端工程部署设置、nginx 代理配置
  14. 清橙OJ A1212. 剪枝
  15. ei拼音的四个声调对应的字_幼儿园学前班拼音教案复习ei以及四声调
  16. python中自定义标识符_python标识符
  17. 实验11-1-4 计算最长的字符串长度-自己的平地摔
  18. Perl-LWP文档
  19. 一段集大成的thymeleaf代码
  20. html的后代选择器,html5怎么使用后代选择器?html选择后代的两种方式详解!

热门文章

  1. 微信二次“回应”iOS 13.2杀后台问题:已找到解决方案
  2. 支付宝下调网银贷款利率 未来持续3年将节省500亿
  3. 一加7喜迎全局DC调光:长时间看屏不再“辣眼”
  4. 三星Galaxy Note 10再曝正面渲染图:居中前置摄像头更顺眼
  5. 拼多多回应“刷单泛滥”一事:对此零容忍 呼吁全社会共同治理
  6. 奔驰宝马路虎等齐降价 最高降幅达8.5万!
  7. 新年就是要你红!华为Mate 20 Pro馥蕾红璨星蓝来袭
  8. Unity中国张俊波:Unity的国际化、本土化、全球化 | 2019WISE超级进化者大会
  9. 辽宁工业大学有没有计算机专业,辽宁工业大学(专业学位)计算机技术考研难吗...
  10. mysql教程查询语句_最全的mysql查询语句整理