This is a creation in Article, where the information may have evolved or changed.
Once the access verification succeeds and becomes the official developer, you may be eager to send a "Hello, Wechat" to your public number server via your mobile phone. However, the previous program has not been able to process the text messages submitted by mobile phone, this article will show how to write a public number program with Golang to receive text messages sent by the mobile phone and reply to response messages.
As described in the Public Platform development Documentation: "When an ordinary user sends a message to a public account, the server sends the XML packet of the post message to the URL that the developer fills in." Let's take a look at this message flow:
The server forwards the message sent by the end user to the public number server via an HTTP POST request, and the message content is wrapped in the body of the HTTP POST requests. The packet is stored in XML format, and the text class message XML example is as follows (quoted from the Public Platform development document):
The meaning of each field in the packet is obvious, and we focus on the content that is filled out in this field, that is, the message content sent by the end user. In order to get this field value, we need to parse the body of the HTTP POST packet sent by the server.
As we mentioned in the article "Access verification", server-initiated requests have validation fields that can be used by the public service to verify that HTTP requests are coming from the server and to avoid a malicious request. The information used to verify the source is not only sent to the public server during the access verification phase, but is also carried in the HTTP request during the subsequent server's message interaction with the public server (note: There is no echostr parameter).
Let's look at the Golang program that receives text messages.
One, receive text messages
The HTTP server used by the public number can follow the server in the "Access authentication" article in the main, and we need to modify the Procrequest function.
In the Procrequest function, we reserve validateurl for verifying that the request is from the server.
Func procrequest (w http. Responsewriter, R *http. Request) {
R.parseform ()
If!validateurl (W, R) {
Log. Println ("WeChat service:this HTTP request is not from Wechat platform!")
Return
}
Log. Println ("Wechat service:validateurl ok!")
...//parsing HTTP Request Body here
}
After verification, we begin to parse the data in the HTTP request body,body in XML format, and we can parse the body through the functions provided in the Golang standard library Encoding/xml package. Encoding/xml the field values in the XML data are parsed into the fields of the struct based on the corresponding relationship between the XML field name and the struct field name or the struct tag (the contents of the inverse single quotation mark referenced by each field in the struct, such as xml: "XML"). So we need to define the struct that should be formatted according to the composition of this XML package, which is defined as follows:
Type textrequestbody struct {
XMLName XML. Name ' xml: ' XML '
Tousername string
Fromusername string
Createtime time. Duration
Msgtype string
Content string
MsgId int
}
Where Fromusername is the sender account, which is an OpenID, each user has a unique OpenID for a public number of concerns. For example: "Tonybai" This user, concerned about "gonuts" and "Godev" two public numbers, "Tonybai" sent to gonuts in the message that OpenID is "tonybai-gonuts", The Tonybai sent to Godev in the message of OpenID is "Tonybai-godev".
The MsgId is a 64-bit integer that can be used for message draining. For an HTTP Post, the server will break the connection if it does not receive a response within five seconds, and re-initiate the request for that message, with a total of three retries. A rigorous public service-side implementation is a message-draining feature that should be implemented.
With the Unmarshal function in the Encoding/xml package, we convert the above XML data to a Textrequestbody instance, with the following code:
//recvtextmsg_unencrypt.go
Func parsetextrequestbody (R *http. Request) *textrequestbody {
body, err: = Ioutil. ReadAll (r.body)
if err! = Nil {
log. Fatal (Err)
return Nil
}
fmt. Println (String (body))
requestbody: = &textrequestbody{}
XML. Unmarshal (body, requestbody)
return requestbody
}
Func procrequest (w http. Responsewriter, R *http. Request) {
R.parseform ()
If!validateurl (W, R) {
Log. Println ("WeChat service:this HTTP request is not from Wechat platform!")
Return
}
if R.method = = "POST" {
Textrequestbody: = Parsetextrequestbody (R)
If textrequestbody! = Nil {
Fmt. Printf ("Wechat service:recv text msg [%s] from user [%s]!",
Textrequestbody.content,
Textrequestbody.fromusername)
}
}
}
Build and execute the program:
$>sudo./recvtextmsg_unencrypt
2014/12/19 08:03:27 Wechat service:start!
Send "Hello, Wechat" via the page debug tool provided by mobile phone or public development platform, we can see the following output:
2014/12/19 08:05:51 Wechat Service:validateurl ok!
WeChat service:recv text msg [Hello, Wechat] from user [obqcwuabkpisabbvd_dezg7q27qi]!
The HTTP packet parsing text of the above receive "Hello, Wechat" text message is as follows (Copy from Wireshark output):
POST/?signature=9b8233c4ef635eaf5b9545dc196da6661ee039b0×tamp=1418976343&nonce=1368270896 HTTP/1.0 \ r \ n
user-agent:mozilla/4.0\r\n
Accept: */*\r\n
host:wechat.tonybai.com\r\n
pragma:no-cache\r\n
content-length:286\r\n
content-type:text/xml\r\n
Public number The HTTP Post response returned by the server to the server is:
http/1.0 ok\r\n
Date:fri, Dec 08:05:51 gmt\r\n
content-length:0\r\n
Content-type:text/plain; charset=utf-8\r\n
Second, response text message
In the above example, the end user sends "Hello, Wechat", although the public number server successfully received the content, but the end user did not get a response, which is obviously not so friendly! Here we will reissue the response of a text message to the end user: Hello, user OpenID.
This type of response message can be carried through the response packet of the HTTP Post request, putting the data into the body of the response package, or, of course, initiating a request (something) separately to the public platform. The definition of a passive text message response in a public platform development document is as follows:
This is very similar to the previous receive message structure, and the field meaning is not self-explanatory. The Marshal (and Marshalindent) functions in Golang Encoding/xml provide the ability to encode a struct as an XML data stream, which is a reverse process of unmarshal, and the code for implementing a reply-to-text response message is as follows:
Type textresponsebody struct {
XMLName XML. Name ' xml: ' XML '
Tousername string
Fromusername string
Createtime time. Duration
Msgtype string
Content string
}
Func maketextresponsebody (fromusername, tousername, content String) ([]byte, error) {
& nbsp; textresponsebody: = &textresponsebody{}
Textresponsebody.fromusername = Fromusername
Textresponsebody.tousername = Tousername
textresponsebody.msgtype = " Text "
textresponsebody.content = Content
textresponsebody.createtime = time. Duration (time. Now (). Unix ())
return XML. Marshalindent (Textresponsebody, "", " ")
}
Func procrequest (w http. Responsewriter, R *http. Request) {
R.parseform ()
If!validateurl (W, R) {
Log. Println ("WeChat service:this HTTP request is not from Wechat platform!")
Return
}
if R.method = = "POST" {
Textrequestbody: = Parsetextrequestbody (R)
If textrequestbody! = Nil {
Fmt. Printf ("Wechat service:recv text msg [%s] from user [%s]!",
Textrequestbody.content,
Textrequestbody.fromusername)
Responsetextbody, err: = Maketextresponsebody (Textrequestbody.tousername,
Textrequestbody.fromusername,
"Hello," +textrequestbody.fromusername)
If err! = Nil {
Log. Println ("Wechat service:maketextresponsebody Error:", err)
Return
}
Fmt. fprintf (W, String (responsetextbody))
}
}
}
After compiling the above program, send a "Hello, Wechat" to the public number by phone or Web debugging tool, the public number will respond to the following message: "Hello, Obqcwuabkpisabbvd_dezg7q27qi", the phone will receive the response correctly.
The above response of the packet capture analysis is as follows. Public number The HTTP Post response returned by the server to the server is:
http/1.0 ok\r\n
Date:fri, Dec 09:03:55 gmt\r\n
content-length:220\r\n
Content-type:text/plain; charset=utf-8\r\n
\ r \ n
Obqcwuabkpisabbvd_dezg7q27qi gh_xxxxxxxx 1418979835 text Hello, Obqcwuabkpisabbvd_dezg7q27qi.
Iii. about Content-type Settings
Although Content-type is: text/plain; Charset=utf-8 's response information can be correctly parsed by the platform, but the HTTP Post request sent to the public server via the crawl platform will look at the server's Content-type as content-type:text/when sending the XML data. Xml. Our response message body is also an XML packet, can we reset Content-type to Text/xml for the response information? We can set it by the following code:
W.header (). Set ("Content-type", "Text/xml")
Fmt. fprintf (W, String (responsetextbody))
But what's strange is that the content-type I get on my AWS EC2 is always "text/plain; Charset=utf-8 ". But with Ngrok mapped to the local port, the grab packet sees the correct "text/xml", and the package that was caught with curl-d xxx.xxx.xxx.xxx testing the public service program on AWS locally is also correct. Through the code does not see what the clue, because the logic explicitly set the header after the Content-type, Go standard library will not be in the format of sniff content.
After mapping the local 80 port via Ngrok, get the HTTP POST response Grab packet parsing text:
http/1.1 ok\r\n
content-type:text/xml\r\n
Date:sat, Dec 04:29:16 gmt\r\n
content-length:220\r\n
XML packets are ignored here.
Iv. use of CDATA
From the capture package, we can see that the XML packet in response to our reply is without CDATA, even if the client receives it without a problem. However, this does not follow the protocol example strictly.
The implication of XML under CDATA is that all tags, entity references are ignored in the markup cdata, and are treated as character data by an XML handler, CDATA in the following form:
文本内容
We try to add a CDATA tag directly to the field value for each text type.
Func Value2cdata (v string) string {
Return " " + v + " "
}
Func maketextresponsebody (Fromusername, tousername, content String) ([]byte, error) {
Textresponsebody: = &textresponsebody{}
Textresponsebody.fromusername = Value2cdata (fromusername)
Textresponsebody.tousername = Value2cdata (tousername)
Textresponsebody.msgtype = Value2cdata ("text")
Textresponsebody.content = Value2cdata (Content)
Textresponsebody.createtime = time. Duration (time. Now (). Unix ())
return XML. Marshalindent (Textresponsebody, "", "")
}
After this modification, we tried to send a message to the public platform, but the result was not correct. The phone cannot receive the response message and says "The public number is temporarily unavailable, please try again later". With the println output body you can see:
<! [cdata[obqcwuabkpisabbvd_dezg7q27qi]]> <![ CDATA[GH_1FD4719F81FE]] > 1419051400 <![ cdata[text]]> <![ Cdata[hello, obqcwuabkpisabbvd_dezg7q27qi]]>
You can see that the left and right angle brackets are escaped to < and > respectively, which is obviously not the result we want. How do I add a CDATA tag? Golang does not directly support XML streams that generate CDATA fields, we can only implement them indirectly. The struct Tag,golang XML package that was mentioned earlier in the definition of a struct specifies: "a field with tag", InnerXml "is written verbatim, not subject to the usual Marsha Lling procedure ". The general meaning is that if the struct tag of a field is ", InnerXml", then the field value marshal is left intact and not submitted to the usual marshalling program. We use InnerXml to implement CDATA markup.
Type textresponsebody struct {
XMLName XML. Name ' xml: ' XML '
Tousername Cdatatext
Fromusername Cdatatext
Createtime time. Duration
Msgtype Cdatatext
Content Cdatatext
}
Type Cdatatext struct {
Text string ' xml: ', InnerXml "'
}
Func Value2cdata (v string) Cdatatext {
return cdatatext{" " + v + " "}
}
After the program is compiled, this time the CDATA tag is correct and the client receives the response information.
Five, using Ngrok to debug the public platform interface locally
In the article "Access verification," We recommend applying for an AWS EC2 to address the development of the public platform interface, but it is not as convenient as local. A web-based open source Tool Ngrok can help us to implement the local debug public platform interface.
The steps for using Ngrok are as follows:
1. Download Ngrok
Ngrok is also implemented using Golang, so the mainstream platform is supported. Ngrok downloaded is an executable binary that can be executed directly (placed under path path).
2, Registered Ngrok
To ngrok.com to register an account, after the successful registration, you can see ngrok.com for your assigned auth token, put this auth token into ~/.ngrok:
Auth_token:your_auth_token
3, the implementation of Ngrok
$ngrok 80
ngrok (Ctrl + C to quit)
Tunnel Status Online
Version 1.7/1.6
Forwarding http://xxxxxxxx.ngrok.com-127.0.0.1:80
Forwarding https://xxxxxxxx.ngrok.com-127.0.0.1:80
Web Interface 127.0.0.1:4040
# Conn 1
AVG Conn Time 1.90ms
where "xxxxxxxx.ngrok.com" is the subdomain assigned to you by Ngrok.
In your Developer center, configure this address in the URL field, submit validation, and verify that the message will flow along the Ngrok tunnel to the 80 port on your local machine.
In addition, local debugging grab packet, to use loopback network port, such as:
$sudo tcpdump-w http.cap-i lo0 TCP port 80
The code covered in this article can be found here.
2014–2015, Bigwhite. All rights reserved.