1. 首页
  2. 综合百科
  3. 形式是什么意思(理解HTTP协议中的多部分)

形式是什么意思(理解HTTP协议中的多部分)

简介:关于形式是什么意思(理解HTTP协议中的多部分)的相关疑问,相信很多朋友对此并不是非常清楚,为了帮助大家了解相关知识要点,小编为大家整理出如下讲解内容,希望下面的内容对大家有帮助!
如果有更好的建议或者想看更多关于综合百科技术大全及相关资讯,可以多多关注茶馆百科网。

前提

之前,写通用HTTP组件时,遇到了媒体类型multipart/form-data的封装问题。本文主要简要介绍HTTP协议中媒体类型multipart/form-data的定义、应用和简单实现。

multipart/form-data的定义

媒体类型multipart/form-data遵循multipartMIME数据流的定义(有关此定义,请参考5.1-RFC2046节),这可能意味着媒体类型multipart/form-data的数据体由多个部分组成,这些部分由固定的边界值分隔。

multipart/form-data请求体布局

多部分/表单数据请求正文的布局如下:

#请求头-这是必要的。需要将content-type指定为multipart/form-data,并指定Content-Type的唯一边界值: multipart/form-data。Boundary=${Boundary}#请求正文-$ {boundary}内容-处置:表单-数据;name=' nameoffile ' Content-type : application/octet-streambytesofile-$ { Boundary } Content-disposition : form-data;name=' nameofpdffilename=' pdf-file . pdf ' Content-type : application/octet-streambytesofpdfile-$ { Boundary } Content-disposition : form-data;name=' key ' content-type : text/plain;charset=UTF-8 TextEncodedInUTF-8-$ { boundary }-媒体类型multipart/form-data与application/x-www-form-urlencoded等其他媒体类型相比,最明显的区别是:

除了将请求头的Content-Type属性指定为multipart/form-data外,还需要定义边界参数的请求体中的请求行数据由多个部分组成,边界参数的取值方式- ${Boundary}}用于分隔各个独立的分部,每个部分必须有请求头Content-disposition 3360 form-data;name=' $ { PART _ NAME },这里的${PART_NAME}需要URL编码,filename字段可以用来表示文件的名称。但是,它的绑定性比name属性差(因为它不能确认本地文件是否可用)。每个部分可以分别定义该部分的内容类型和数据体请求体,以边界参数的值mode-$ { boundary }-作为结束标记{ % notewarningflat % } RFC 7578/中提到的两个多部分/过期表单数据的使用包括使用Content-Transfer-Encoding请求头,在此不再展开,以及使用multipart/mixed(一个“名称”对应于多个二进制文件的场景){%endnote%}用于通过单个表单传输多个二进制文件

特别是:

如果一个部分的内容是文本,其内容类型是text/plain,则可以指定相应的字符集,如Content-Type : text/plain;Charset=UTF-8可以通过_charset_ attribute指定默认字符集,用法如下:content-disposition : form-data;name=' _ charset _ ' UTF-8-ABCDE-Content-disposition : form-data;Name=' field 'TextCodedinutf-8.ABCDE-

Boundary参数取值规约

边界参数值约定如下:

在英语中,Boundary的值必须以双杠开始,称为前导连字符边界的值不能超过70个字符,除了前导连字符。Boundary的值不能包含HTTP协议或URL禁止的具有特殊含义的字符,如英文冒号3360。在每个- ${Boundary}}之前,默认情况下必须是CRLF。如果某个部分的文本类型请求体以CRLF结尾,那么在请求体的二进制格式中必须有两个crlf。如果某一部分的请求体不以CRLF结尾,则只能有一个CRLF。这两种情况分别称为显式和隐式分隔符,比较抽象。请看下面的例子:# request header content-type : multi part/data;boundary='-abcdefg '-abcdefgContent-disposition : form-data;名称=

4;x"Content-type:text/plain;charset=asciiItdoesNOTendwithalinebreak#<===这里没有CRLF,隐式类型--abcdefgContent-Disposition:form-data;name="y"Content-type:text/plain;charset=asciiItDOESendwithalinebreak#<===这里有CRLF,显式类型--abcdefg##直观看隐式类型的CRLFItdoesNOTendwithalinebreakCRLF--abcdefg##直观看显式类型的CRLFItDOESendwithalinebreakCRLFCRLF--abcdefg

实现multipart/form-data媒体类型的POST请求

这里只针对低JDK版本的HttpURLConnection和高JDK版本内置的HttpClient编写multipart/form-data媒体类型的POST请求的HTTP客户端,其他如自定义Socket实现可以依照类似的思路完成。先引入org.springframework.boot:spring-boot-starter-web:2.6.0做一个简单的控制器方法:

@RestControllerpublicclassTestController{@PostMapping(path="/test")publicResponseEntity<?>test(MultipartHttpServletRequestrequest){returnResponseEntity.ok("ok");}}

Postman的模拟请求如下:

后台控制器得到的请求参数如下:

后面编写的客户端可以直接调用此接口进行调试。

封装请求体转换为字节容器的模块

这里的边界值全用显式实现,边界值直接用固定前缀加上UUID生成即可。简单实现过程中做了一些简化:

只考虑提交文本表单数据和二进制(文件)表单数据基于上一点,每个部分都明确指定Content-Type这个请求头文本编码固定为UTF-8

编写一个MultipartWriter:

publicclassMultipartWriter{privatestaticfinalCharsetDEFAULT_CHARSET=StandardCharsets.UTF_8;privatestaticfinalbyte[]FIELD_SEP=":".getBytes(StandardCharsets.ISO_8859_1);privatestaticfinalbyte[]CR_LF="\r\n".getBytes(StandardCharsets.ISO_8859_1);privatestaticfinalStringTWO_HYPHENS_TEXT="--";privatestaticfinalbyte[]TWO_HYPHENS=TWO_HYPHENS_TEXT.getBytes(StandardCharsets.ISO_8859_1);privatestaticfinalStringCONTENT_DISPOSITION_KEY="Content-Disposition";privatestaticfinalStringCONTENT_TYPE_KEY="Content-Type";privatestaticfinalStringDEFAULT_CONTENT_TYPE="multipart/form-data;boundary=";privatestaticfinalStringDEFAULT_BINARY_CONTENT_TYPE="application/octet-stream";privatestaticfinalStringDEFAULT_TEXT_CONTENT_TYPE="text/plain;charset=UTF-8";privatestaticfinalStringDEFAULT_CONTENT_DISPOSITION_VALUE="form-data;name=\"%s\"";privatestaticfinalStringFILE_CONTENT_DISPOSITION_VALUE="form-data;name=\"%s\";filename=\"%s\"";privatefinalMap<String,String>headers=newHashMap<>(8);privatefinalList<AbstractMultipartPart>parts=newArrayList<>();privatefinalStringboundary;privateMultipartWriter(Stringboundary){this.boundary=Objects.isNull(boundary)?TWO_HYPHENS_TEXT+UUID.randomUUID().toString().replace("-",""):boundary;this.headers.put(CONTENT_TYPE_KEY,DEFAULT_CONTENT_TYPE+this.boundary);}publicstaticMultipartWriternewMultipartWriter(Stringboundary){returnnewMultipartWriter(boundary);}publicstaticMultipartWriternewMultipartWriter(){returnnewMultipartWriter(null);}publicMultipartWriteraddHeader(Stringkey,Stringvalue){if(!CONTENT_TYPE_KEY.equalsIgnoreCase(key)){headers.put(key,value);}returnthis;}publicMultipartWriteraddTextPart(Stringname,Stringtext){parts.add(newTextPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE,name),DEFAULT_TEXT_CONTENT_TYPE,this.boundary,text));returnthis;}publicMultipartWriteraddBinaryPart(Stringname,byte[]bytes){parts.add(newBinaryPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE,name),DEFAULT_BINARY_CONTENT_TYPE,this.boundary,bytes));returnthis;}publicMultipartWriteraddFilePart(Stringname,Filefile){parts.add(newFilePart(String.format(FILE_CONTENT_DISPOSITION_VALUE,name,file.getName()),DEFAULT_BINARY_CONTENT_TYPE,this.boundary,file));returnthis;}privatestaticvoidwriteHeader(Stringkey,Stringvalue,OutputStreamout)throwsIOException{writeBytes(key,out);writeBytes(FIELD_SEP,out);writeBytes(value,out);writeBytes(CR_LF,out);}privatestaticvoidwriteBytes(Stringtext,OutputStreamout)throwsIOException{out.write(text.getBytes(DEFAULT_CHARSET));}privatestaticvoidwriteBytes(byte[]bytes,OutputStreamout)throwsIOException{out.write(bytes);}interfaceMultipartPart{voidwriteBody(OutputStreamos)throwsIOException;}@RequiredArgsConstructorpublicstaticabstractclassAbstractMultipartPartimplementsMultipartPart{protectedfinalStringcontentDispositionValue;protectedfinalStringcontentTypeValue;protectedfinalStringboundary;protectedStringgetContentDispositionValue(){returncontentDispositionValue;}protectedStringgetContentTypeValue(){returncontentTypeValue;}protectedStringgetBoundary(){returnboundary;}publicfinalvoidwrite(OutputStreamout)throwsIOException{writeBytes(TWO_HYPHENS,out);writeBytes(getBoundary(),out);writeBytes(CR_LF,out);writeHeader(CONTENT_DISPOSITION_KEY,getContentDispositionValue(),out);writeHeader(CONTENT_TYPE_KEY,getContentTypeValue(),out);writeBytes(CR_LF,out);writeBody(out);writeBytes(CR_LF,out);}}publicstaticclassTextPartextendsAbstractMultipartPart{privatefinalStringtext;publicTextPart(StringcontentDispositionValue,StringcontentTypeValue,Stringboundary,Stringtext){super(contentDispositionValue,contentTypeValue,boundary);this.text=text;}@OverridepublicvoidwriteBody(OutputStreamos)throwsIOException{os.write(text.getBytes(DEFAULT_CHARSET));}@OverrideprotectedStringgetContentDispositionValue(){returncontentDispositionValue;}@OverrideprotectedStringgetContentTypeValue(){returncontentTypeValue;}}publicstaticclassBinaryPartextendsAbstractMultipartPart{privatefinalbyte[]content;publicBinaryPart(StringcontentDispositionValue,StringcontentTypeValue,Stringboundary,byte[]content){super(contentDispositionValue,contentTypeValue,boundary);this.content=content;}@OverridepublicvoidwriteBody(OutputStreamout)throwsIOException{out.write(content);}}publicstaticclassFilePartextendsAbstractMultipartPart{privatefinalFilefile;publicFilePart(StringcontentDispositionValue,StringcontentTypeValue,Stringboundary,Filefile){super(contentDispositionValue,contentTypeValue,boundary);this.file=file;}@OverridepublicvoidwriteBody(OutputStreamout)throwsIOException{try(InputStreamin=newFileInputStream(file)){finalbyte[]buffer=newbyte[4096];intl;while((l=in.read(buffer))!=-1){out.write(buffer,0,l);}out.flush();}}}publicvoidforEachHeader(BiConsumer<String,String>consumer){headers.forEach(consumer);}publicvoidwrite(OutputStreamout)throwsIOException{if(!parts.isEmpty()){for(AbstractMultipartPartpart:parts){part.write(out);}}writeBytes(TWO_HYPHENS,out);writeBytes(this.boundary,out);writeBytes(TWO_HYPHENS,out);writeBytes(CR_LF,out);}}

这个类已经封装好三种不同类型的部分请求体实现,forEachHeader()方法用于遍历请求头,而最终的write()方法用于把请求体写入到OutputStream中。

HttpURLConnection实现

实现代码如下(只做最简实现,没有考虑容错和异常处理):

publicclassHttpURLConnectionApp{privatestaticfinalStringURL="http://localhost:9099/test";publicstaticvoidmain(String[]args)throwsException{MultipartWriterwriter=MultipartWriter.newMultipartWriter();writer.addTextPart("name","throwable").addTextPart("domain","vlts.cn").addFilePart("ico",newFile("I:\\doge_favicon.ico"));DataOutputStreamrequestPrinter=newDataOutputStream(System.out);writer.write(requestPrinter);HttpURLConnectionconnection=(HttpURLConnection)newjava.net.URL(URL).openConnection();connection.setRequestMethod("POST");connection.addRequestProperty("Connection","Keep-Alive");//设置请求头writer.forEachHeader(connection::addRequestProperty);connection.setDoInput(true);connection.setDoOutput(true);connection.setConnectTimeout(10000);connection.setReadTimeout(10000);DataOutputStreamout=newDataOutputStream(connection.getOutputStream());//设置请求体writer.write(out);StringBuilderbuilder=newStringBuilder();BufferedReaderreader=newBufferedReader(newInputStreamReader(connection.getInputStream(),StandardCharsets.UTF_8));Stringline;while(Objects.nonNull(line=reader.readLine())){builder.append(line);}intresponseCode=connection.getResponseCode();reader.close();out.close();connection.disconnect();System.out.printf("响应码:%d,响应内容:%s\n",responseCode,builder);}}

执行响应结果:

响应码:200,响应内容:ok

可以尝试加入两行代码打印请求体:

MultipartWriterwriter=MultipartWriter.newMultipartWriter();writer.addTextPart("name","throwable").addTextPart("domain","vlts.cn").addFilePart("ico",newFile("I:\\doge_favicon.ico"));DataOutputStreamrequestPrinter=newDataOutputStream(System.out);writer.write(requestPrinter);

控制台输出如下;

JDK内置HttpClient实现

JDK11+内置了HTTP客户端实现,具体入口是java.net.http.HttpClient,实现编码如下:

publicclassHttpClientApp{privatestaticfinalStringURL="http://localhost:9099/test";publicstaticvoidmain(String[]args)throwsException{HttpClienthttpClient=HttpClient.newBuilder().connectTimeout(Duration.of(10,ChronoUnit.SECONDS)).build();MultipartWriterwriter=MultipartWriter.newMultipartWriter();writer.addTextPart("name","throwable").addTextPart("domain","vlts.cn").addFilePart("ico",newFile("I:\\doge_favicon.ico"));ByteArrayOutputStreamout=newByteArrayOutputStream();writer.write(out);HttpRequest.BuilderrequestBuilder=HttpRequest.newBuilder();writer.forEachHeader(requestBuilder::header);HttpRequestrequest=requestBuilder.uri(URI.create(URL)).method("POST",HttpRequest.BodyPublishers.ofByteArray(out.toByteArray())).build();HttpResponse<String>response=httpClient.send(request,HttpResponse.BodyHandlers.ofString());System.out.printf("响应码:%d,响应内容:%s\n",response.statusCode(),response.body());}}

内置的HTTP组件几乎都是使用Reactive编程模型,使用的API都是相对底层,灵活性比较高但是易用性不高。

小结

媒体类型multipart/form-data常用于POST方法下的HTTP请求,至于作为HTTP响应的场景相对少见。

本文主要介绍了关于形式是什么意思(理解HTTP协议中的多部分)的相关养殖或种植技术,综合百科栏目还介绍了该行业生产经营方式及经营管理,关注综合百科发展动向,注重系统性、科学性、实用性和先进性,内容全面新颖、重点突出、通俗易懂,全面给您讲解综合百科技术怎么管理的要点,是您综合百科致富的点金石。
以上文章来自互联网,不代表本人立场,如需删除,请注明该网址:http://seotea.com/article/88962.html