Post请求的两种编码格式

application/x-www-form-urlencoded和multipart/form-data

在常见业务开发中,POST请求常常在这些地方使用:前端表单提交时、调用接口代码时和使用Postman测试接口时。我们下面来一一了解:

一、前端表单提交时

application/x-www-form-urlencoded

表单代码:

<form action="http://localhost:8888/task/" method="POST">
  First name: <input type="text" name="firstName" value="Mickey&">
  <br>
  Last name: <input type="text" name="lastName" value="Mouse ">
  <br>
  <input type="submit" value="提交">
</form>

通过测试发现可以正常访问接口,在Chrome的开发者工具中可以看出,表单上传编码格式为application/x-www-form-urlencoded(Request Headers中),参数的格式为key=value&key=value

  • 我们可以看出,服务器知道参数用符号&间隔,如果参数值中需要&,则必须对其进行编码。编码格式就是application/x-www-form-urlencoded将键值对的参数用&连接起来,如果有空格,将空格转换为+加号;有特殊符号,将特殊符号转换为ASCII HEX)。
  • application/x-www-form-urlencoded是浏览器默认的编码格式。对于Get请求,是将参数转换?key=value&key=value格式,连接到url后

ps:可以在这个网址测试表单:http://www.runoob.com/try/try.php?filename=tryhtml_form_submit

multipart/form-data

那么当服务器使用multipart/form-data接收POST请求时,服务器怎么知道每个参数的开始位置和结束位置呢?

<form action="http://localhost:8888/task/" method="POST" enctype="multipart/form-data">
  First name: <input type="text" name="firstName" value="Mickey&">
  <br>
  Last name: <input type="text" name="lastName" value="Mouse ">
  <br>
 <input type="submit" value="提交">
</form>

我们在开发者工具中可以看出multipart/form-data不会对参数编码,使用的boundary(分割线),相当于&boundary的值是----Web**AJv3

文件上传

上传文件也要指定编码格式为multipart/form-data

<form action="http://localhost:8888/testFile" enctype="multipart/form-data" method="POST">
  <input type="file" name="file">
  <input type="submit" value="提交">
</form>

如果是SpringMVC项目,要服务器能接受multipart/form-data类型参数,还要在spring上下文配置以下内容,SpringBoot项目则不需要

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="defaultEncoding" value="utf-8"></property>
</bean>

我们可以通过FormData对象模拟表单提交,用原始的XMLHttpRequest来发送数据,让我们可以在Chrome开发工具中查看到具体格式:

<form id="form">
  First name: <input type="text" name="firstName" value="Mickey">
  <br>
  Last name: <input type="text" name="lastName" value="Mouse">
  <br>
  <input type="file" name="file">
  <br>
</form>

<button onclick="submitForm()">提交</button>

<script>
function submitForm() {
  var formElement = document.getElementById("form");
  var xhr = new XMLHttpRequest();
  xhr.open("POST", "/task/testFile");
  xhr.send(new FormData(formElement));
}
</script>

格式如下:

二、调用接口代码时

1、在代码中使用application/x-www-form-urlencoded编码格式设置Request属性调用接口,可以如下实现:

private static String doPost(String strUrl, String content) {
  String result = "";
  try {
    URL url = new URL(strUrl);
    //通过调用url.openConnection()来获得一个新的URLConnection对象,并且将其结果强制转换为HttpURLConnection.
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
    urlConnection.setRequestMethod("POST");
    //设置连接的超时值为30000毫秒,超时将抛出SocketTimeoutException异常
    urlConnection.setConnectTimeout(30000);
    //设置读取的超时值为30000毫秒,超时将抛出SocketTimeoutException异常
    urlConnection.setReadTimeout(30000);
    //将url连接用于输出,这样才能使用getOutputStream()。getOutputStream()返回的输出流用于传输数据
    urlConnection.setDoOutput(true);
    //设置通用请求属性为默认浏览器编码类型
    urlConnection.setRequestProperty("content-type", "application/x-www-form-urlencoded");
    //getOutputStream()返回的输出流,用于写入参数数据。
    OutputStream outputStream = urlConnection.getOutputStream();
    outputStream.write(content.getBytes());
    outputStream.flush();
    outputStream.close();
    //此时将调用接口方法。getInputStream()返回的输入流可以读取返回的数据。
    InputStream inputStream = urlConnection.getInputStream();
    byte[] data = new byte[1024];
    StringBuilder sb = new StringBuilder();
    //inputStream每次就会将读取1024个byte到data中,当inputSteam中没有数据时,inputStream.read(data)值为-1
    while (inputStream.read(data) != -1) {
      String s = new String(data, Charset.forName("utf-8"));
      sb.append(s);
    }
    result = sb.toString();
    inputStream.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
  return result;
}
public static void main(String[] args) {
  String str = doPost("http://localhost:8888/task/", "firstName=Mickey%26&lastName=Mouse ");
  System.out.println(str);
}

2、在代码中使用multipart/form-data编码格式设置Request属性调用接口时,其中boundary的值可以在设置Content-Type时指定,让服务器知道如何拆分它接受的参数。通过以下代码的调用接口:

private static String doPost(String strUrl, Map<String, String> params, String boundary) {
  String result = "";
  try {
    URL url = new URL(strUrl);
    HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
    urlConnection.setRequestMethod("POST");
    urlConnection.setConnectTimeout(30000);
    urlConnection.setReadTimeout(30000);
    urlConnection.setDoOutput(true);
    //设置通用请求属性为multipart/form-data
    urlConnection.setRequestProperty("content-type", "multipart/form-data;boundary=" + boundary);
    DataOutputStream dataOutputStream = new DataOutputStream(urlConnection.getOutputStream());
    for (String key : params.keySet()) {
      String value = params.get(key);
      //注意!此处是\r(回车:将当前位置移到本行开头)、\n(换行:将当前位置移到下行开头)要一起使用
      dataOutputStream.writeBytes("--" + boundary + "\r\n");
      dataOutputStream.writeBytes("Content-Disposition: form-data; name=\"" + encode(key) + "\"\r\n");
      dataOutputStream.writeBytes("\r\n");
      dataOutputStream.writeBytes(encode(value) + "\r\n");
    }
    //最后一个分隔符的结尾后面要跟"--"
    dataOutputStream.writeBytes("--" + boundary + "--");
    dataOutputStream.flush();
    dataOutputStream.close();
    InputStream inputStream = urlConnection.getInputStream();
    byte[] data = new byte[1024];
    StringBuilder sb = new StringBuilder();
    while (inputStream.read(data) != -1) {
    String s = new String(data, Charset.forName("utf-8"));
    sb.append(s);
  }
    result = sb.toString();
    inputStream.close();
  } catch (Exception e) {
    e.printStackTrace();
  }
  return result;
}
private static String encode(String value) throws UnsupportedEncodingException {
  return URLEncoder.encode(value, "UTF-8");
}
public static void main(String[] args) {
  Map<String, String> params = new HashMap<>();
  params.put("firstName", "Mickey");
  params.put("lastName", "Mouse");
  //自定义boundary,有两个要求:使用不会出现在发送到服务器的HTTP数据中的值;并在请求消息中的分割位置都使用相同的值
  String boundary = "abcdefg";
  String str = doPost("http://localhost:8888/testFile", params, boundary);
  System.out.println(str);
}

通过debug,可以看出dataOutputStream的值如下:

三、使用Postman测试接口时

1、POST请求 -> Body -> x-www-form-urlencoded

当切换为x-www-form-urlencoded时,Headers会自动添加Content-Type:application/x-www-form-urlencoded

当请求Send后,此时点Code,可以查看到和Chrome开发工具中(Request Headers处的Content-Type和Form Data)一样的数据

2、POST请求 -> Body -> form-data

相当于html表单请求,value可为Text或文件。

可以不用手动指定编码格式,也可以指定编码为multipart/form-data

划线处的分割线应该是被省略了。

可以更改左上角的类型,来查看相应的Headers代码,常见的是下面三种:

Java OK HTTP

JavaScript Jquery AJAX

JavaScript XHR

总结

POST请求的两种编码格式:application/x-www-urlencoded是浏览器默认的编码格式,用于键值对参数,参数之间用&间隔;multipart/form-data常用于文件等二进制,也可用于键值对参数,最后连接成一串字符传输(参考Java OK HTTP)。除了这两个编码格式,还有application/json也经常使用。

接口代码

@RequestMapping("/task")
public class TaskController {
  @RequestMapping("/")
  public String index(String firstName, String lastName) {
    return firstName + lastName;
  }
  @RequestMapping("/testFile")
  public String testFile(String firstName, String lastName, MultipartFile file) {
    String result = firstName + lastName;
    if (file != null) {
      result += (file.getOriginalFilename() != null) ? file.getOriginalFilename() : "";
    }
      return result;
  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352