get请求方式可以带方法体吗?

前言: 前几天有同学在postman中发送get请求时,把请求参数放入了请求体中。然后后端使用了@RequestBody来接收请求参数,它确实是可以这样用的!postman测试API的响应也是正常的。但是前端使用get方式传递参数,却报 400 错误。所以最终还是把@RequestBody换成了@RequestParam。或者也可以把请求方法改成post。不过第一次见到get请求方式这样使用,我感觉很古怪。因为按照我以前的学习来看,它是不行的,至少不符合规范。如果使用html的表单,是无法做到的,所以前面的前端发送的请求无法被正确接收,导致了400,但是在postman中,它目前是可行的(以前的版本是不支持的)。也就是说,如果我们不是使用网页(浏览器)来发送get请求那它应该就是可以使用的。 但是这样的解释,不够具体,我们可以利用所学知识,来深入理解为什么?不过在那之前,先来了解一下基础知识吧!
在这里插入图片描述

HTTP请求报文

HTTP请求报文的格式如下,不同请求方式的报文都满足这一格式,但是具体上会有一些差异。
这里提一下,为什么要有结构呢?这实际上是协议规定的,因为这样程序才可以解析,如果一个没有规律的报文,尽管可以发送,但是那是无法正确解析的。

HTTP请求方式主要有:get、post、put、delete等,这里以get和post最为常用。
在这里插入图片描述

我们这里就只简单介绍get和post的一些特点:
get方式:
1.get方式是发送请求得到实体(得到服务器的数据),它应该响应一个资源。
2.get方式请求的参数会跟在URL后面,以?来分隔URL和参数,如果有多个参数,那么参数之间使用&连接。并且,整个URL加参数需要使用url encode编码方式编码,get请求方式的参数可以在浏览器的输入框看到。
3.get请求方式的url长度有所限制,有人说是2KB,有人说是4KB。但是这个只是浏览器的规定(因为浏览器的输入框的长度是有限制的,它不可能非常长的。),所以实际上,如果不经过浏览器来发送get请求,应该是没有这个限制的,但是应该也没有人会发送一个100M的请求参数吧,哈哈。
这应该是比较权威的解释了:Http get方法提交的数据大小长度并没有限制,Http协议规范没有对URL长度进行限制。目前说的get长度有限制,是特定的浏览器及服务器对它的限制。

post方式:
1.post方式是向服务器发送请求,上传实体。
2.post方式的请求数据是放入请求体中的,所以用户无法看到,通常认为它比较安全。(但是这也是针对浏览器的,如果不使用浏览器,即使是get请求用户也是看不到的请求参数的。)
3.post请求方式的长度也是没有限制的,但是它实际受限制于服务器的性能限制。

报文到底长啥样?——有一美人兮,见之不忘。

前面光是理论的介绍,还是很难让人认识到具体的区别,顶多就是死记硬背一下那几点总结了。但是计算机网络是分层的,站在HTTP层看HTTP并不能看到所有的信息,让我们再往下一层,站在TCP的角度来看吧。让我们来一窥它的真面目吧!

提供一个demo

package dragon.net;

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

public class Server {
	private final String BLANK = " ";    //空格符
	private final String CRLF = "\r\n";  //回车换行符
	
	public static void main(String[] args) {
		Server server = new Server();
		server.start();
	}
	
	public void start() {
		System.out.println("服务启动。。。");
		try (ServerSocket server = new ServerSocket(8899)){
			while (true) {
				Socket client = server.accept();
				InputStream in = new BufferedInputStream(client.getInputStream());       //获取HTTP请求报文
				byte[] b = new byte[5*1024];  //这里因为网络流读取不是以 -1 来判断结束,处理比较复杂,
				int len = in.read(b);         //所以我使用一个大点的字节数组,直接读取整个报文
				//不对请求报文进行处理,直接响应一个固定的数据。
				OutputStream out = new BufferedOutputStream(client.getOutputStream());   //发送HTTP响应报文
				
				Charset UTF_8 = Charset.forName("UTF-8");  //使用 UTF-8 作为字符集
				String json = "{\"name\":\"龙林夕\", \"words\":\"I love you yesterday and today\"}";
				byte[] responseBody = json.getBytes(UTF_8);
				
				StringBuilder header = new StringBuilder();
				byte[] responseHeader = header.append("HTTP/1.0").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("application/json").append(CRLF)
					.append("Content-Length:").append(BLANK).append(responseBody.length).append(CRLF).append(CRLF)
					.toString()
					.getBytes(UTF_8);
				
				//发送响应报文
				out.write(responseHeader);
				out.write(responseBody);
				out.flush();  //刷新输出流。
				//打印请求报文
				System.out.println(new String(b, 0, len, UTF_8));
				
				//关闭客户端连接
				client.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("服务结束!");
	}
	
	//获取时间
		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);
		}
}

说明: 这个程序启动之后,通过浏览器访问 localhost:8899 即可看到下面图的结果,就会返回一个响应报文,报文数据是一个json,并打印出请求报文。

运行结果
浏览器上面的结果:(注意get请求的参数)
在这里插入图片描述
控制台上面的结果:
这里可以看到有两个请求,但是我们不需要管第二个,它是浏览器自动发送的,目的是请求网站的图标。 第一个请求是我们需要关注的,可以看到整个完整的HTTP报文。其实这里不太能看出来它的结构,可以使用notepad++来打开(显示所有字符)观看。

注意: 划线处,我访问的路径是 / ,并且请求参数是在URL后面的,但是它已经被 url 编码了,所以看起来很奇怪,但是只要解码就能看出来很上面的请求参数是一致的。
在这里插入图片描述

在打印报文下面添加如下语句,并且修改报文的路径(确保该文件夹存在),运行即可在相应路径下看到报文,使用notepad++打开,并且显示全部字符可看到报文的结构,注意那个黄色的小点是表示空格。

//把报文写入文件,并且使用追加的方式,否则请求图片的报文会覆盖我的请求参数的报文
try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
		new FileOutputStream("D:/DragonFile/DBC/msg.txt", true), UTF_8))) {
	writer.write(new String(b, 0, len, UTF_8));
}

注意:浏览器发送的get请求是没有请求体的,所以最后一个 CRLF 之后报文就结束了。
在这里插入图片描述

让我们跳出浏览器的规范,使用其它方式发送请求

现在,我要使用postman来发送请求了,但是不知为何,post发送的请求,我无法接收,如果我尝试打印请求,那么会报一个错误,程序也就崩溃了。
在这里插入图片描述
这个似乎表明,我并没有发送请求!但是当我注释打印请求的代码段时,它就可以工作正常了。那这样我就看不到报文了!我已经写到这里了,没有想会遇到这个问题,我怎么能放弃呢!幸好我还有最后一招!—— 抓包。只要请求发送出去,必有数据包!抓住它,不久一目了然了嘛!

因为最近准备抓csdn的blink,学着试了一下抓包,但是望着抓到的4000多个包,只剩下一脸懵逼!app抓包这个东西,似乎不是那么简单的,我也不会分析这些网络包,完全浪费了我一天的时间。 但是我简单尝试了使用 Fiddler抓包。使用 Fiddler 抓包的方法,可以去看一篇博客,基本上就可以掌握最基础的抓包方法了,这里我也是刚学,就不多介绍了。似乎也可以通过HttpClient来做到,但是这里还是软件使用起来比较顺手,尽管因为上面那个问题我差点放弃了。

Fiddler 抓包

在这里插入图片描述
但是,我发现只要我打开了Fiddler抓包,我就可以正常使用 postman 了,这是一个问题,不过暂时我不需要管它。
所以,Fiddler 只管开启抓包,我还是使用postman发送请求来打印报文。

抓包软件的结果,可以发现前三个请求路径有参数,body里面也有数据,总之,它不是正常的请求参数,至少web上是很少见的!
在这里插入图片描述

postman 测试

注意,这里的前提是 Fiddler 开启抓包,虽然原因是什么暂时还不清楚。

1.get方式,把参数加在请求体的位置上。
在这里插入图片描述

在这里插入图片描述
2.post方式,由于我同时选择了Params和Body两个选项,所以发送的报文居然变成了get和post的混合形式。

在这里插入图片描述

在这里插入图片描述

3.post方式,去掉Body部分后,post请求参数加在url后面的形式。注意最下面的 Content-Length:0,虽然没有请求体了,但是它还是要有的,只是值为0。

在这里插入图片描述
在这里插入图片描述

说明

可以发现,这似乎全部乱了套了,但是它是可以的,web包括的范围不止浏览器那一块,应该也包含其它领域,毕竟使用HTTP协议的地方还是很多的。感觉今天的一番探索之后,收获了很多,但是我们可以更进一步,发送更加自定义的报文吗?

更进一步

下面我提供了一个小的demo,通过它和前面的那个demo,一起来进行简单的测试。
这里我把这个请求方法的名字给改了,然后经过测试,也是可以打印结果的。但是这个明显是不规范的,因为不可能有这种请求方法的,哈哈!

提供一个demo
package dragon.net;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.Charset;

public class Client {
	private static final String BLANK = " ";    //空格符
	private static final String CRLF = "\r\n";  //回车换行符
	
	public static void main(String[] args) {
		try (Socket socket = new Socket("127.0.0.1", 8899)) {
			InputStream in = new BufferedInputStream(socket.getInputStream());      //获取输入流
			OutputStream out = new BufferedOutputStream(socket.getOutputStream());  //获取输出流
			
			StringBuilder msg = new StringBuilder();
			msg.append("FUCK").append(BLANK).append("/name=%E9%BE%99%E6%9E%97%E5%A4%95").append("HTTP/1.1").append(CRLF)
			   .append("User-Agent").append(": ").append("CrazyDragon").append(CRLF)
			   .append("accept").append(": ").append("*/*").append(CRLF)
			   .append("Host").append(": ").append("localhost:8899").append(CRLF)
			   .append("Content-Type").append(": ").append("application/x-www-form-urlencoded").append(CRLF)
			   .append("Content-Length").append(": ").append("32").append(CRLF)
			   .append(CRLF)
			   .append("name=%E9%BE%99%E6%9E%97%E5%A4%95");
			
			Charset UTF_8 = Charset.forName("UTF-8");
			byte[] data = msg.toString().getBytes(UTF_8);
			out.write(data);
			out.flush();  //刷新输出流
			
			byte[] b = new byte[5*1024];  //我这个值其实设置的偏大,对于文本数据来说。
			int len = in.read(b);
			System.out.println(new String(b, 0, len, UTF_8));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

在这里插入图片描述
在这里插入图片描述

总结

HTTP报文从TCP的角度来说,只是一串字节流。TCP—传输控制协议,提供的是面向连接、可靠的字节流服务。既然是字节流,虽然协议是那样规定的,但是确实是可以有一些不常规的使用方法。
虽然上面那个自定义一定是非法的,但是感觉很有意思!get和post两种方式是我们最常使用的,所以形成了一些特定的用法,如get方式不携带请求体。但是我刚才查阅资料发现,似乎协议也没规定不能这样用啊!但是还是保持默认的规则比较适合,否则可能会出现一些奇怪的问题。正如我们开头所提到的那个问题。
协议是灵活的,你完全可以自己定义一套简单的协议。反正基于TCP层面,也不止HTTP一种协议,它也不一定是最好的,只是对于某些场景很适合。

  • 17
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值