Http学习之组装报文

作者:CrazyDragon_King 时间:2021-12-30 07:23:18 

前面介绍了一些,基本的概念和需要具备的编程知识。下面开始来进行代码的编写,前面已经提到了最终的代码会是一个的http服务器的小demo–一个图床网站。

主要目标介绍

这里主要涉及的知识点就是解析报文和组装报文。 解析报文就是指解析HTTP请求报文,你需要知道报文请求的资源是什么。 组装报文就是指组装HTTP响应报文,你需要返回客户请求的相应资源。

目标分析

解析报文,需要获取完整的报文,利用报文的特定结构,获取报文里面的信息。然后依据这些信息,先客户端返回响应报文。这里涉及到自己解析报文,比较有难度,因为需要报文的结构特定。上一篇博客已经简单介绍过了(这里只是处理一些简单的请求和响应报文,不是那种特别复杂的,毕竟只是学习,没有必要自己和自己过不去!)

组装报文,需要将客户需要的信息组装好,然后发送给客户端。报文会由客户端(通常是浏览器)自动解析,这里就不需要解析了,只是把报文发送给客户端。对于编程来说,只是涉及到IO流的处理而已。所以,组装报文比较简单一些。

所以,这篇博客就先只介绍如何组装报文(注意这里虽然是简单的报文,但也是符合规定的报文)。

组装报文

我们来回顾一下前面介绍的知识,通常一个完整的报文包括报文头和报文体。(当然了,GET请求方式是没有请求体的。)

主要的代码就是下面三行了。

out.write(header);        //写入Http报文头部部分
out.write(content);       //写入Http报文数据部分
out.flush();              //刷新输出流,确保缓冲区内数据已经发送完成

是不是感觉很神奇,所谓的HTTP报文,在TCP这个层次来看,不过就是一个字节流。(这里 header 和 content 在网络上是可以看成串行的流。)

启动服务类

这个类和平时使用的 ServerSocket 类用法没有什么区别,就是使用多线程来处理每一个客户端的连接。启动一个ServerSocket,监听10000端口。

package com.dragon;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class HttpServer {
private static ServerSocket server;

/**
* 启动服务
* */
public void start() {
   try {
   server = new ServerSocket(10000);
   System.out.println("服务启动成功...");
   this.receiveRequest();
   } catch (IOException e) {
   e.printStackTrace();
   System.out.println("服务启动失败!");
   }
}

/**
* 接收请求
* */
public void receiveRequest() {
ExecutorService pool = Executors.newFixedThreadPool(10);
while (true) {
try {
Socket client = server.accept();
System.out.println("用户"+client.getInetAddress().toString()+"建立连接" + client.toString());
pool.submit(new Connection(client));  //使用线程处理每一个请求。
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* 停止服务
* 注:一般是不需要关闭服务的。
* */
public void stop() {
try {
if (server != null) {
server.close();
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("服务器关闭失败!");
}
}
}

连接类

对于每一个客户端的连接,获取用户的请求,并返回响应。因为只是一个简单的模拟,这里其实获取用户的请求也不进行处理(因为处理需要解析请求报文),对于任何的请求返回的响应都是同一个。所以,它实际上还具有一个非常有趣的特点–消灭了404。 相信经常使用浏览器的人应该都知道404这个错误吧,404的意思是对于当前的请求没有找到请求的资源。所以,通常可以看到 Not Found 这两个英文单词,当然了也可以自定义成其它的形式。因为这个程序,只具有接收请求,返回响应的基本功能,所以,我间接消灭了404,哈哈!

注:头部字段中,我只是返回了几个必要的头部。因为HTTP头部还是比较多的,有些也不是必要的。具体的信息,可以参考一些专业的书籍来了解更多的知识或者直接阅读这方面的权威–RFC文档,哈哈(不过我也没有看,就是瞅了一眼。)。

package com.dragon;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

public class Connection implements Runnable {
private static final String BLANK = " ";
private static final String CRLF = "\r\n";
private byte[] content;
private byte[] header;
private Socket client;

public Connection(Socket client) {
this.client = client;
}

@Override
public void run() {
//这里不对请求进行处理,只是收到请求之后会进行响应,而不管是什么请求
//这里模拟服务器最原始的功能:请求、响应。
this.response();
System.out.println("线程执行结束了!");
}

/**
* 接收请求信息,这里只是一个简单的模拟,这里只能接收get请求。
* @throws IOException
* */
public String getRequestInfo(InputStream in) throws IOException {
//只读取第一行,这是我们需要的全部内容
StringBuilder requestLine = new StringBuilder(80);
while (true) {
int c = in.read();
if (c == '\r' || c == '\n' || c == -1) break;
requestLine.append((char)c);
}
return requestLine.toString();
}

/**
* 响应信息
* */
public void response() {
InputStream in = null;
OutputStream out = null;
try {
in = new BufferedInputStream(client.getInputStream());
out = new BufferedOutputStream(client.getOutputStream());   //获取输出流
String requestInfo = this.getRequestInfo(in);           //如果不读取客户端发来的数据,服务器就会出错。
System.out.println(requestInfo);
} catch (IOException e1) {
e1.printStackTrace();
}       //获取输入流

//响应体数据
File file = new File("D:/DragonFile/target/attitude.jpg");
String contentType = null;  //文件的 MIME 类型
try {
content = Files.readAllBytes(file.toPath());           //使用 Files 工具类,一次性读取文件
contentType = Files.probeContentType(file.toPath());   //获取文件的 MIME 类型
long length = file.length();                           //获取文件字节长度
header = this.getHeader(contentType, length);          // 填充响应头
} catch (IOException e) {
e.printStackTrace();
}

try {
out.write(header);        //写入Http报文头部部分
out.write(content);       //写入Http报文数据部分
out.flush();              //刷新输出流,确保缓冲区内数据已经发送完成

System.out.println("报文总大小(字节):" + (header.length + content.length));
} catch (IOException e) {
e.printStackTrace();
System.out.println("客户断开连接或者发送失败!");
} finally {
//此处关闭 client 会导致程序出现问题,但是原因不清楚。
try {
if (client != null) {
client.close();
}
System.out.println("请求结束了");
} catch (IOException e) {
e.printStackTrace();
}
}
   }

//响应头
private byte[] getHeader(String contentType, long length) {
return new StringBuilder()
.append("HTTP/1.1").append(BLANK).append(200).append(BLANK).append("OK").append(CRLF)  // 响应头部
.append("Server:"+"CrazyDragon").append(CRLF)
.append("Date:").append(BLANK).append(this.getDate()).append(CRLF)
.append("Content-Type:").append(BLANK).append(contentType).append(CRLF) //文件的 Content-Type 可通过Java获取。
.append("Content-Length:").append(BLANK).append(length).append(CRLF).append(CRLF)
.toString()
.getBytes(Charset.forName("UTF-8"));
}

//获取时间
private String getDate() {
Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT  
return format.format(date);
}
}

主类

package com.dragon;

public class Test {
public static void main(String[] args) {
HttpServer httpServer = new HttpServer();
httpServer.start();
}
}

运行结果

打印输出:

Http学习之组装报文

浏览器访问:

Http学习之组装报文

注:上面可以看到会有两个请求,这是因为浏览器访问的时候,会请求网站的图标,通常的路径为 /favicon.ico,这里似乎是因为缓存,没有看到它的请求,如果存在请求行应该为:GET /favicon.ico HTTP/1.1。

随便访问一个地址,看一看没有404的网站!

Http学习之组装报文

访问结果

Http学习之组装报文

注意:这里可能会遇到异常。 但是这个对于程序的运行结果没有影响。报错的原因处在这句话:out.wirte(content);

Http学习之组装报文

可能的原因有一下几点:

①:服务器的并发连接数超过了其承载量,服务器会将其中一些连接Down掉;

②:客户关掉了浏览器,而服务器还在给客户端发送数据;

③:浏览器端按了Stop 按钮。

④:用servlet的outputstream输出流下载图片时,当用户点击取消也会报这个错误。

来源:https://blog.csdn.net/qq_40734247/article/details/106030901

标签:Http,报文,组装
0
投稿

猜你喜欢

  • JavaGUI常用三种布局使用介绍

    2023-05-19 08:01:06
  • Android集成zxing扫码框架功能

    2021-09-27 02:37:17
  • Unity Shader实现3D翻页效果

    2021-10-07 10:03:45
  • 使用C#开发OPC Server服务器源码解析

    2021-05-30 04:30:14
  • 详解SpringBoot自定义配置与整合Druid

    2023-07-24 20:20:28
  • Android横竖屏幕切换生命周期详解

    2023-11-02 03:29:45
  • 基于Mybatis-Plus的CRUD的实现

    2023-09-10 14:38:45
  • android倒计时控件示例

    2022-12-01 05:34:53
  • Java字节码ByteBuddy使用及原理解析上

    2023-08-23 19:33:05
  • Java设计模式之java备忘录模式详解

    2023-08-22 19:31:07
  • springboot反爬虫组件kk-anti-reptile的使用方法

    2022-01-09 14:12:59
  • Flutter利用Hero组件实现自定义路径效果的动画

    2023-06-25 13:46:29
  • Android简单实现圆盘抽奖界面

    2022-07-25 08:35:27
  • Java线程安全的计数器简单实现代码示例

    2023-11-09 15:41:57
  • 关于feign.codec.DecodeException异常的解决方案

    2022-01-28 15:50:19
  • Entity Framework主从表数据加载方式

    2022-03-10 21:44:14
  • Kotlin 匿名类实现接口和抽象类的区别详解

    2021-09-04 07:14:58
  • Java新手学习之IO流的简单使用

    2023-08-09 15:57:33
  • 详解java倒计时三种简单实现方式

    2023-11-11 16:47:38
  • java中Object类4种方法详细介绍

    2023-11-03 16:06:12
  • asp之家 软件编程 m.aspxhome.com