第一篇 网站基础知识 第5章 自己动手实现HTTP协议
第5章 自己动手实现HTTP协议
我们知道HTTP协议是在应用层解析内容的,只需要按照它的报文的格式封装和解析数据就可以了,具体的传输还是使用的Socket,在第4章NioServer的基础上自己做一个简单的实现了HTTP协议的例子。
因为HTTP协议是在接收到数据之后才会用到的,所以我们只需要修改NioServer中的Handler就可以了,在修改后的HttpHandler中首先获取到请求报文并打印出报文的头部(包含首行)、请求的方法类型、Url和Http版本,最后将接收到的请求报文信息封装到响应报文的主体中返回给客户端。这里的HttpHandler使用了单独的线程来执行,而且把SelectionKey中操作类型的选择也放在了HttpHandler中,不过具体处理过程和前面的NioServer没有太大的区别,代码如下:
HttpServer:
public class HttpServer {
public static void main(String[] args) throws Exception{
//创建ServerSocketChannel,监听8080端口
ServerSocketChannel ssc=ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
//设置为非阻塞模式
ssc.configureBlocking(false);
//为ssc注册选择器
Selector selector=Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
//创建处理器
while(true){
// 等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,如果传入0或者不传参数将一直阻塞
if(selector.select(3000)==0){
continue;
}
// 获取待处理的SelectionKey
Iterator<SelectionKey> keyIter=selector.selectedKeys().iterator();
while(keyIter.hasNext()){
SelectionKey key=keyIter.next();
// 启动新线程处理SelectionKey
new Thread(new HttpHandler(key)).run();
// 处理完后,从待处理的SelectionKey迭代器中移除当前所使用的key
keyIter.remove();
}
}
}
}
HttpHander:
public class HttpHandler implements Runnable { private int bufferSize = 1024;
private String localCharset = "UTF-8";
private SelectionKey key; public HttpHandler(SelectionKey key) {
this.key = key;
} public void handleAccept() throws IOException {
SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
clientChannel.configureBlocking(false);
clientChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
} public void handleRead() throws IOException {
// 获取channel
SocketChannel sc = (SocketChannel) key.channel();
// 获取buffer并重置
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
// 没有读到内容则关闭
if (sc.read(buffer) == -1) {
sc.close();
} else {
// 接收请求数据
buffer.flip();
String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
// 控制台打印请求报文头
String[] requestMessage = receivedString.split("\r\n");
for (String s : requestMessage) {
System.out.println(s);
// 遇到空行说明报文头已经打印完
if (s.isEmpty())
break;
} // 控制台打印首行信息
String[] firstLine = requestMessage[0].split(" ");
System.out.println();
System.out.println("Method:\t" + firstLine[0]);
System.out.println("url:\t" + firstLine[1]);
System.out.println("HTTP Version:\t" + firstLine[2]);
System.out.println(); // 返回客户端
StringBuilder sendString = new StringBuilder();
sendString.append("HTTP/1.1 200 OK\r\n");// 响应报文首行,200表示处理成功
sendString.append("Content-Type:text/html;charset=" + localCharset + "\r\n");
sendString.append("\r\n");// 报文头结束后加一个空行 sendString.append("<html><head><title>显示报文</title></head><body>");
sendString.append("接收到请求报文是:<br/>");
for (String s : requestMessage) {
sendString.append(s + "<br/>");
}
sendString.append("</body></html>");
buffer = ByteBuffer.wrap(sendString.toString().getBytes(localCharset));
sc.write(buffer);
sc.close();
}
} public void run() {
try {
// 接收到连接请求时
if (key.isAcceptable()) {
handleAccept();
}
// 读数据
if (key.isReadable()) {
handleRead();
}
} catch (IOException ex) {
ex.printStackTrace();
}
} }
整个过程非常简单,按照报文的格式来读取和发送就可以了,接收到数据后按“\r\n”分割成每一行,在空行之前都是报文头(包含首行),空行下面如果有内容就是报文的主体,因为这里是Get请求所以就没有主体了,首行使用空格分割后可以得到请求的方法、Url和Http的版本,如果需要请求头的值只需要把头部的每一行用冒号分割开就行了。下面就来看一下运行效果,首先启动程序,然后在浏览器中输入http://localhost:8080/发起请求,这时控制台就会打印出如下信息(不同的环境打印的结果会不同)。
控制台报文显示:
浏览器报文显示:
这里的功能并不能真正处理请求,实际处理中应该根据不同的Url和不同的请求方法进行不同的处理并返回不同的响应报文,另外这里的请求报文也必须在bufferSize(1024)范围内,如果太长就会接收不全,而且也不能返回图片等流类型的数据(流类型只需要在响应报文中写清楚Content-Type的类型,并将相应数据写入报文的主体就可以了)。
第一篇 网站基础知识 第5章 自己动手实现HTTP协议的更多相关文章
- 第一篇 网站基础知识 第3章 DNS的设置
第3章 DNS的设置 3.1 DNS解析 3.2 Windows 7设置DNS服务器 3.3Windows设置本机域名和IP的对应关系 在自己的电脑里也可以设置域名和IP的对应关系,具体设置是在C:\ ...
- 第一篇 网站基础知识 第4章 Java中Socket的用法
第4章 Java中Socket的用法 4.1 普通Socket的用法 Java中的网络通信是通过Socket实现的,Socket分为ServetSocket和Socket两大类,ServetSocke ...
- 第一篇 网站基础知识 第7章 Tomcat分析
7.1 Tomcat的顶层结构及启动过程 7.1.1 Tomcat的顶层结构 Tomcat中最顶层的容器叫Server,代表整个服务器,Server中包含至少一个Service,用于具体提供服务.Se ...
- LWJGL3的内存管理,第一篇,基础知识
LWJGL3的内存管理,第一篇,基础知识 为了讨论LWJGL在内存分配方面的设计,我将会分为数篇随笔分开介绍,本篇将主要介绍一些大方向的问题和一些必备的知识. 何为"绑定(binding)& ...
- SLAM第一篇:基础知识
无论在室内.野外.空中还是水下,SLAM是机器人进入未知环境遇到的第一个问题.本期给大家介绍SLAM的基础知识:传感器与视觉SLAM框架 近来年,智能机器人技术在世界范围内得到了大力发展.人们致力于把 ...
- spring cloud系列教程第四篇-Eureka基础知识
通过前三篇文章学习,我们搭建好了两个微服务工程.即:order80和payment8001这两个服务.有了这两个基础的框架之后,我们将要开始往里面添加东西了.还记得分布式架构的几个维度吗?我们要通过一 ...
- 第一部分 CLR基础:第3章 共享程序集和强命名程序集
第一部分 CLR基础:第3章 共享程序集和强命名程序集
- Jquery真的不难~第一回 编程基础知识
Jquery真的不难~第一回 编程基础知识 回到目录 前言 说Jquery之前,先来学习一下Javascript(以后简称为JS)语言中的基础知识问题,其时对于每种编程语言来说基础知识都是大同小异 ...
- Angular 4 学习笔记 从入门到实战 打造在线竞拍网站 基础知识 快速入门 个人感悟
最近搞到手了一部Angular4的视频教程,这几天正好有时间变学了一下,可以用来做一些前后端分离的网站,也可以直接去打包web app. 环境&版本信息声明 运行ng -v @angular/ ...
随机推荐
- Java 代码块详解
注:本文出自博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 注:本文原链接:https://www.cnblogs.com/chloneda/p/java-c ...
- Quartz.NET - 教程 5: 简单触发器
译者注: 目录在这 Quartz.NET 3.x 教程 原文在这 Lesson 5: SimpleTrigger 如果你需要在特定的时间点执行一次作业, 或者在特定的时间点执行一次作业, 然后在特定的 ...
- opencv —— 官方 示例程序
OpenCV 官方提供的示例程序,具体位于...\opencv\sources\samples\cpp 目录下. ...\opencv\sources\samples\cpp\tutorial_cod ...
- sql关系型运算符优先级高到低为:not >and> or
今天在做项目的时候发现一个查询的结果不太对. 随后拿出sql仔细端详一番,where条件中发现一个条件本应该是 …… xx in (‘13’,‘14’)……,却写成了…… xx = ‘13’ or x ...
- Redis的各个数据的类型基本命令
什么是Redis: 概念: Redis (REmote DIctionary Server) 是用 C 语言开发的一个开源的高性能键值对(key-value)数据库. 特征:1. 数据间没有必然的关联 ...
- Windows电脑最最最常用快捷键
快捷键组合 作用 Ctrl+A 全选 Ctrl+S 保存 Ctrl+Z 撤销 Ctrl+X 剪切 Ctrl+C 复 ...
- Python异常类型及包含关系
Python异常类型及包含关系,设计异常捕获时参考: BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- ...
- css揭秘 一
当某些值相互依赖是,应该把它们的相互关系用代码表达出来 font-size: 20px; line-height: 1.5; // 行高是字体的1.5倍 当改变某个参数时候,做到只改尽量少的地方,最好 ...
- NC反弹shell的几种方法
假如ubuntu.CentOS为目标服务器系统 kali为攻击者的系统,ip为:192.168.0.4,开放7777端口且没被占用 最终是将ubuntu.CentOS的shell反弹到kali上 正向 ...
- json转dataset的另外一种解析方式自动生成guid强关联
/// <summary> /// 将json字符串自动转成dataset,并且自动补全主子关联关系, /// Guid,FKGuid /// Author:lijia /// date: ...