一起看一下golang的HTTP包怎么write Request信息
先看一下看golang http Request的struct,不解释,慢慢看(HTTP权威指南,RFC文档)
typeRequeststruct{ //MethodspecifiestheHTTPmethod(GET,POST,PUT,etc.). //ForclientrequestsanemptystringmeansGET. Methodstring //URLspecifieseithertheURIbeingrequested(forserver //requests)ortheURLtoaccess(forclientrequests). // //ForserverrequeststheURLisparsedfromtheURI //suppliedontheRequest-LineasstoredinRequestURI.For //mostrequests,fieldsotherthanPathandRawQuerywillbe //empty.(SeeRFC2616,Section5.1.2) // //Forclientrequests,theURL'sHostspecifiestheserverto //connectto,whiletheRequest'sHostfieldoptionally //specifiestheHostheadervaluetosendintheHTTP //request. URL*url.URL //Theprotocolversionforincomingrequests. //ClientrequestsalwaysuseHTTP/1.1. Protostring//"HTTP/1.0" ProtoMajorint//1 ProtoMinorint//0 //Aheadermapsrequestlinestotheirvalues. //Iftheheadersays // // accept-encoding:gzip,deflate // Accept-Language:en-us // Connection:keep-alive // //then // // Header=map[string][]string{ // "Accept-Encoding":{"gzip,deflate"},// "Accept-Language":{"en-us"},// "Connection":{"keep-alive"},// } // //HTTPdefinesthatheadernamesarecase-insensitive. //Therequestparserimplementsthisbycanonicalizingthe //name,makingthefirstcharacterandanycharacters //followingahyphenuppercaseandtherestlowercase. // //Forclientrequestscertainheadersareautomatically //addedandmayoverridevaluesinHeader. // //SeethedocumentationfortheRequest.Writemethod. HeaderHeader //Bodyistherequest'sbody. // //Forclientrequestsanilbodymeanstherequesthasno //body,suchasaGETrequest.TheHTTPClient'sTransport //isresponsibleforcallingtheClosemethod. // //ForserverrequeststheRequestBodyisalwaysnon-nil //butwillreturnEOFimmediatelywhennobodyispresent. //TheServerwillclosetherequestbody.TheServeHTTP //Handlerdoesnotneedto. Bodyio.ReadCloser //ContentLengthrecordsthelengthoftheassociatedcontent. //Thevalue-1indicatesthatthelengthisunknown. //Values>=0indicatethatthegivennumberofbytesmay //bereadfromBody. //Forclientrequests,avalueof0meansunknownifBodyisnotnil. ContentLengthint64 //TransferEncodingliststhetransferencodingsfromoutermostto //innermost.Anemptylistdenotesthe"identity"encoding. //TransferEncodingcanusuallybeignored;chunkedencodingis //automaticallyaddedandremovedasnecessarywhensendingand //receivingrequests. TransferEncoding[]string //Closeindicateswhethertoclosetheconnectionafter //replyingtothisrequest(forservers)oraftersending //therequest(forclients). Closebool //ForserverrequestsHostspecifiesthehostonwhichthe //URLissought.PerRFC2616,thisiseitherthevalueof //the"Host"headerorthehostnamegivenintheURLitself. //Itmaybeoftheform"host:port". // //ForclientrequestsHostoptionallyoverridestheHost //headertosend.Ifempty,theRequest.Writemethoduses //thevalueofURL.Host. Hoststring //Formcontainstheparsedformdata,includingboththeURL //field'squeryparametersandthePOSTorPUTformdata. //ThisfieldisonlyavailableafterParseFormiscalled. //TheHTTPclientignoresFormandusesBodyinstead. Formurl.Values //PostFormcontainstheparsedformdatafromPOSTorPUT //bodyparameters. //ThisfieldisonlyavailableafterParseFormiscalled. //TheHTTPclientignoresPostFormandusesBodyinstead. PostFormurl.Values //MultipartFormistheparsedmultipartform,includingfileuploads. //ThisfieldisonlyavailableafterParseMultipartFormiscalled. //TheHTTPclientignoresMultipartFormandusesBodyinstead. MultipartForm*multipart.Form //Trailerspecifiesadditionalheadersthataresentaftertherequest //body. // //ForserverrequeststheTrailermapinitiallycontainsonlythe //trailerkeys,withnilvalues.(Theclientdeclareswhichtrailersit //willlatersend.)WhilethehandlerisreadingfromBody,itmust //notreferenceTrailer.AfterreadingfromBodyreturnSEOF,Trailer //canbereadagainandwillcontainnon-nilvalues,iftheyweresent //bytheclient. // //ForclientrequestsTrailermustbeinitializedtoamapcontaining //thetrailerkeystolatersend.Thevaluesmaybenilortheirfinal //values.TheContentLengthmustbe0or-1,tosendachunkedrequest. //AftertheHTTPrequestissentthemapvaluescanbeupdatedwhile //therequestbodyisread.OncethebodyreturnSEOF,thecallermust //notmutateTrailer. // //FewHTTPclients,servers,orproxiessupportHTTPtrailers. TrailerHeader //RemoteAddrallowsHTTPserversandothersoftwaretorecord //thenetworkaddressthatsenttherequest,usuallyfor //logging.ThisfieldisnotfilledinbyReadRequestand //hasnodefinedformat.TheHTTPserverinthispackage //setsRemoteAddrtoan"IP:port"addressbeforeinvokinga //handler. //ThisfieldisignoredbytheHTTPclient. RemoteAddrstring //RequestURIistheunmodifiedRequest-URIofthe //Request-Line(RFC2616,Section5.1)assentbytheclient //toaserver.UsuallytheURLfieldshouldbeusedinstead. //ItisanerrortosetthisfieldinanHTTPclientrequest. RequestURIstring //TLSallowsHTTPserversandothersoftwaretorecord //informationabouttheTLSconnectiononwhichtherequest //wasreceived.ThisfieldisnotfilledinbyReadRequest. //TheHTTPserverinthispackagesetsthefieldfor //TLS-enabledconnectionsbeforeinvokingahandler; //otherwiseitleavesthefieldnil. //ThisfieldisignoredbytheHTTPclient. TLS*tls.ConnectionState }
再来具体分析一下http request write的具体执行流程
func(req*Request)write(wio.Writer,usingProxybool,extraHeadersHeader)error{ host:=req.Host ifhost==""{ ifreq.URL==nil{ returnerrors.New("http:Request.WriteonRequestwithnoHostorURLset") } host=req.URL.Host } ruri:=req.URL.RequestURI() //代理模式的时候ruri需要加上协议名http/https等 ifusingProxy&&req.URL.Scheme!=""&&req.URL.Opaque==""{ ruri=req.URL.Scheme+"://"+host+ruri }elseifreq.Method=="CONNECT"&&req.URL.Path==""{ //CONNECTrequestsnormallygivejustthehostandport,notafullURL. ruri=host } //TODO(bradfitz):escapeatleastnewlinesinruri? //WrapthewriterinabufioWriterifit'snotalreadybuffered. //Don'talwayscallNewWriter,asthatforcesabytes.Buffer //andothersmallbufioWriterstohaveaminimum4kbuffer //size. //创建一个Writer,往里面写内容 varbw*bufio.Writer if_,ok:=w.(io.ByteWriter);!ok{ bw=bufio.NewWriter(w) w=bw } //写http最开始数据 _,err:=fmt.Fprintf(w,"%s%sHTTP/1.1\r\n",valueOrDefault(req.Method,"GET"),ruri) iferr!=nil{ returnerr } //Headerlines写Host内容 _,err=fmt.Fprintf(w,"Host:%s\r\n",host) iferr!=nil{ returnerr } //UsethedefaultUserAgentunlesstheHeadercontainsone,which //maybeblanktonotsendtheheader. //这东西的数据如下: /*constdefaultUserAgent="Go1.1packagehttp"*/ userAgent:=defaultUserAgent ifreq.Header!=nil{ ifua:=req.Header["User-Agent"];len(ua)>0{ userAgent=ua[0] } } ifuserAgent!=""{ _,"User-Agent:%s\r\n",userAgent) iferr!=nil{ returnerr } } //ProcessBody,ContentLength,Close,Trailer //封装的transferWriter结构 tw,err:=newTransferWriter(req) iferr!=nil{ returnerr } err=tw.WriteHeader(w) iferr!=nil{ returnerr } err=req.Header.WriteSubset(w,reqWriteExcludeHeader) iferr!=nil{ returnerr } ifextraHeaders!=nil{ err=extraHeaders.Write(w) iferr!=nil{ returnerr } } _,err=io.WriteString(w,"\r\n") iferr!=nil{ returnerr } //Writebodyandtrailer err=tw.WriteBody(w) iferr!=nil{ returnerr } ifbw!=nil{ returnbw.Flush() } returnnil }
再来看看transferWriter结构相关的操作:
//主要用于写HTTP的Body,Trailer typetransferWriterstruct{ Methodstring Bodyio.Reader BodyCloserio.Closer ResponseToHEADbool ContentLengthint64//-1meansunknown,0meansexactlynone Closebool TransferEncoding[]string TrailerHeader }
创建transferWriter的过程:
funcnewTransferWriter(rinterface{})(t*transferWriter,errerror){ t=&transferWriter{} //Extractrelevantfields atLeastHTTP11:=false switchrr:=r.(type){ case*Request: ifrr.ContentLength!=0&&rr.Body==nil{ returnnil,fmt.Errorf("http:Request.ContentLength=%dwithnilBody",rr.ContentLength) } t.Method=rr.Method t.Body=rr.Body t.BodyCloser=rr.Body t.ContentLength=rr.ContentLength t.Close=rr.Close t.TransferEncoding=rr.TransferEncoding t.Trailer=rr.Trailer atLeastHTTP11=rr.ProtoAtLeast(1,1) ift.Body!=nil&&len(t.TransferEncoding)==0&&atLeastHTTP11{ ift.ContentLength==0{ //Testtoseeifit'sactuallyzeroorjustunset. varbuf[1]byte n,rerr:=io.ReadFull(t.Body,buf[:]) ifrerr!=nil&&rerr!=io.EOF{ t.ContentLength=-1 t.Body=&errorReader{rerr} }elseifn==1{ //Oh,guessthereisdatainthisBodyReaderafterall. //TheContentLengthfieldjustwasn'tset. //StichtheBodybacktogetheragain,re-attachingour //consumedbyte. t.ContentLength=-1 t.Body=io.MultiReader(bytes.NewReader(buf[:]),t.Body) }else{ //Bodyisactuallyempty. t.Body=nil t.BodyCloser=nil } } ift.ContentLength<0{ t.TransferEncoding=[]string{"chunked"} } } case*Response: ifrr.Request!=nil{ t.Method=rr.Request.Method } t.Body=rr.Body t.BodyCloser=rr.Body t.ContentLength=rr.ContentLength t.Close=rr.Close t.TransferEncoding=rr.TransferEncoding t.Trailer=rr.Trailer atLeastHTTP11=rr.ProtoAtLeast(1,1) t.ResponseToHEAD=noBodyExpected(t.Method) } //SanitizeBody,TransferEncoding ift.ResponseToHEAD{ t.Body=nil ifchunked(t.TransferEncoding){ t.ContentLength=-1 } }else{ if!atLeastHTTP11||t.Body==nil{ t.TransferEncoding=nil } ifchunked(t.TransferEncoding){ t.ContentLength=-1 }elseift.Body==nil{//nochunking,nobody t.ContentLength=0 } } //SanitizeTrailer if!chunked(t.TransferEncoding){ t.Trailer=nil } returnt,nil }
最后再看看WriteBody的操作:
func(t*transferWriter)WriteBody(wio.Writer)error{ varerrerror varncopyint64 //Writebody写body的操作在这里 ift.Body!=nil{ ifchunked(t.TransferEncoding){ cw:=internal.NewChunkedWriter(w) _,err=io.Copy(cw,t.Body) iferr==nil{ err=cw.Close() } }elseift.ContentLength==-1{ ncopy,err=io.Copy(w,t.Body) }else{ ncopy,io.LimitReader(t.Body,t.ContentLength)) iferr!=nil{ returnerr } varnextraint64 nextra,err=io.Copy(IoUtil.Discard,t.Body) ncopy+=nextra } iferr!=nil{ returnerr } iferr=t.BodyCloser.Close();err!=nil{ returnerr } } if!t.ResponseToHEAD&&t.ContentLength!=-1&&t.ContentLength!=ncopy{ returnfmt.Errorf("http:ContentLength=%dwithBodylength%d",t.ContentLength,ncopy) } //TODO(petar):Placetrailerwritercodehere. ifchunked(t.TransferEncoding){ //WriteTrailerheader ift.Trailer!=nil{ iferr:=t.Trailer.Write(w);err!=nil{ returnerr } } //Lastchunk,emptytrailer _,"\r\n") } returnerr }
自己实现HTTP服务器可以借鉴一下此处代码
原文链接:https://www.f2er.com/go/190094.html