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

    技术2022-07-10  143

    前言: 前几天有同学在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=龙林夕").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=龙林夕"); 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一种协议,它也不一定是最好的,只是对于某些场景很适合。

    Processed: 0.014, SQL: 10