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
投稿

猜你喜欢

  • 经典排序算法之冒泡排序(Bubble sort)代码

    2021-06-08 06:06:18
  • JAVA十大排序算法之桶排序详解

    2022-11-08 01:07:47
  • Java类的初始化顺序知识点总结

    2021-09-23 23:19:26
  • 简单了解springboot加载配置文件顺序

    2022-06-19 13:36:29
  • sqlite数据库的介绍与java操作sqlite的实例讲解

    2023-05-09 03:07:40
  • spreadsheetgear插件屏蔽鼠标右键的方法

    2022-06-21 14:04:18
  • Java thread.isInterrupted() 返回值不确定结果分析解决

    2023-11-09 19:27:09
  • Android自定义ListView实现下拉刷新上拉加载更多

    2021-08-20 21:17:04
  • Java中的static关键字你了解多少

    2022-05-21 11:25:51
  • C# 遍历文件夹子目录下所有图片及遍历文件夹下的文件

    2022-03-12 02:55:47
  • Java8中Optional类型和Kotlin中可空类型的使用对比

    2023-07-29 07:49:21
  • 详解java操作Redis数据库的redis工具(RedisUtil,jedis工具JedisUtil,JedisPoolUtil)

    2023-07-29 11:31:36
  • Spring组件开发模式支持SPEL表达式

    2023-09-05 11:53:31
  • 关于idea的gitignore文件编写及解决ignore文件不生效问题

    2023-02-28 02:04:13
  • C# yield关键字详解

    2021-08-01 13:06:06
  • Java实现统计字符串出现的次数

    2022-12-19 01:06:52
  • java启动参数之谜的排查过程

    2023-02-18 19:47:50
  • Android 开发中Volley详解及实例

    2023-07-21 00:12:19
  • 实现Android 滑动退出Activity的功能

    2023-04-24 03:00:34
  • Redis之GEO存储地理位置信息的使用

    2023-12-22 14:29:49
  • asp之家 软件编程 m.aspxhome.com