We write HTTP / 1.1 & HTTP / 2 client and server on Golang

Golang is a great programming language with a wide range of features. This article shows you how to write a client and a server for Go for HTTP / 1.1 and HTTP / 2 protocols on Go.

Skillbox recommends: Practical course "Python-developer from scratch."

We remind: for all readers of "Habra" – a discount of 10,000 rubles when writing to any Skillbox course using the promotional code "Habr"

HTTP / 2 is the second major version of the HTTP network protocol used to access the World Wide Web. The protocol is based on SPDY. Learn more about this version on GitHub.

HTTP / 1.1 Server

The code below demonstrates how to write an HTTP / 1.1 server on Go. The main function starts the HTTP (S) service with port 9191 and the path / hello / sayHello. echoPayload is responsible for echo logic, analyzing incoming traffic and responding accordingly. If necessary, echoPayload can be modified.

package main
 
import (
fmt
"io / ioutil"
"log"
"net / http"
)
 
func main () {
http.HandleFunc ("/ hello / sayHello", echoPayload)
log.Printf ("Go Backend: {HTTPVersion = 1}; serving on https: // localhost: 9191 / hello / sayHello")
log.Fatal (http.ListenAndServeTLS (": 9191", "./cert/server.crt", "./cert/server.key", nil))
}
 
func echoPayload (w http.ResponseWriter, req * http.Request) {
log.Printf ("Request connection:% s, path:% s", req.Proto, req.URL.Path[1:])
defer req.Body.Close ()
contents, err: = ioutil.ReadAll (req.Body)
if err! = nil {
log.Fatalf ("Oops! Failed reading body of the request.  n% s", err)
http.Error (w, err.Error (), 500)
}
fmt.Fprintf (w, "% s  n", string (contents))

Since the HTTP (S) service is already running, you need to provide a certificate and server key. Both objects are stored in the cert directory with the names server.crt and server.key.

An example of a certificate and key is below.

./cert/server.crt

—– BEGIN CERTIFICATE —–
MIID + zCCAuOgAwIBAgIJAPsvGCCAC2i + MA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
VQQGEwJMSzEQMA4GA1UECAwHV2VzdGVybjEQMA4GA1UEBwwHQ29sb21ibzESMBAG
A1UECgwJTERDTEFLTUFMMRQwEgYDVQQLDAtFbmdpbmVlcmluZzESMBAGA1UEAwwJ
bG9jYWxob3N0MSIwIAYJKoZIhvcNAQkBFhNsZGNsYWttYWxAZ21haWwuY29tMB4X
DTE5MDQyMDA1MjczM1oXDTIwMDQxOTA1MjczM1owgZMxCzAJBgNVBAYTAkxLMRAw
DgYDVQQIDAdXZXN0ZXJuMRAwDgYDVQQHDAdDb2xvbWJvMRIwEAYDVQQKKAlMRENM
QUtNQUwxFDASBgNVBAsMC0VuZ2luZWVyaW5nMRIwEAYDVQQDDAlsb2NhbGhvc3Qx
IjAgBgkqhkiG9w0BCQEWE2xkY2xha21hbEBnbWFpbC5jb20wggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9PKAOlJcOBUI9CGnVjMjQHNRqYv01CaUdC4 / e
YFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK2WP3V0FLv5hM7Nxsgf25WNHa
zi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaHy5DNH0kPnSiPiy7PRKToEUn6
oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1vDCqrJbr3YwWOzFCq8kEJFslK
B7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lvSJZ5S / LZD5teDDg6Ax3Mvthj
kMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXjsmATEgtLAgMBAAGjUDBOMB0G
A1UdDgQWBBQ1CmWXmrHOv6b8f763 / bk80EpbajAfBgNVHSMEGDAWgBQ1CmWXmrHO
v6b8f763 / bk80EpbajAMBgNVHRMEBTADAQH / MA0GCSqGSIb3DQEBCwUAA4IBAQAH
D51Uoe2K4N9 / GxRgww5mMW2dUJ7Hc / tGsr / J1fNqHY8SXNAn5i + GwI + xBvwxFHL3
KZHbfq7eYDE5EItt3cZp5ySSscdTEay9ReH2 + 8k32gpH46CMwPV3XvtQuBVVAC4u
szrq1eWKhYI2zf4iUVpwvq89OynVGIp0atng + q3A2cBhi3NGo6Ho1s2rywQyqiq8
up4PUSVQ6WBoJFx5PEEDxD84VMS7Pan6dT34b9n56tq5R06retZTUZ8jMM88CGX4
88pSPU + XImp6DdNVBmW6Lz76jiSNHLkZGm4jumjeyUGzBjBEBOgSegeWlinMtWE9
gaVxeUHrqHk8xzwJ4oIu
—– END CERTIFICATE —–

./cert/server.key

—– BEGIN PRIVATE KEY —–
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC9PKAOlJcOBUI9
CGnVjMjQHNRqYv01CaUdC4 / eYFyegxLpoMpYvEC + nYlHT2j7BOhQBV + TkH1D4YOK
2WP3V0FLv5hM7Nxsgf25WNHazi2DTBvcBgB9sDJA / avIvF + 63 + Btnyggp3xq6MaH
y5DNH0kPnSiPiy7PRKToEUn6oqPnB10rRBFZqs3ePmEDxVL3T / TUZSXR3P95fV1v
DCqrJbr3YwWOzFCq8kEJFslKB7GSEKpPgmK0g5krmAQqUOuCJ3 / xFlCP4trKg / lv
SJZ5S / LZD5teDDg6Ax3MvthjkMh9 / OM5GGTTjRwhct9dHjFI8POj + TMbLZvoPVXj
smATEgtLAgMBAAECggEAbaS2yDvn2cPKQTqit4y + vXY2zP1V4GkaNd4BGcOTZnRj
fOIg25EXoln8tEiadva888BpREKvkakUYlraxPDVcGIuiEOk42nd7Io97R0Q2cY7
ThxcJHb2ZxmTctdSUCBvFJTm1ySzve3pOb0ExRSfbGCOo7zs / kKzmZKK3qFlffGS
Ga9O7hyLOuXPU22CM + 5Lq0JPTER73z0DpAweZc0L14j6dzhcG3qUwk0K6K47VZgE
NhEORul7xDj91bh2iEoSbaQe8HxLaMQoMXOC / 9oey2UKKTe9WZE3 + XCvg + vkw / sS
biQ + b4EZ9LuhAhCZ0UE6 + y7PZY + 8G / YsbGg0Zo8cAQKBgQDyTuG47rWBgbdHsEB /
MSKGU6w + a1SdLk6jG + Enji5Q624 / h0xt5nF9ah2eRf3Rlhn9WEKM / uE9ouEODBKE
8rnIDsjufEMI8moPEloRBSsxPNw + fNMSSCZjL + qPtTJUbRio7WA23sfdnE57ygBa
wlPQ9UBBWSm2se4veEZtHjtngQKBgQDH7gnH5Att6ZYazRTgD72g0C5v1l4LYVEQ
jxdBcs6TJA8wHfifZ45F67W95QunmM813UxfS + ybdjytlb8 / lmi2BnK6lDx5HWIL
31jnbg2CxCrNv9oZLjKVDmkp4WUcEp5W33R1 / MGDTRfyARP + 6QYQO / ATMdqtm5Uu
cD6clrL4ywKBgQCQ0niy0WmGaAMlQ8CoxLM / 2c6 + 1 + OQtlalwkoGHEKudqhELBeQ
MAVw0fW13Vtg4vfRpejQ4J26 + xjMDocbEv / bBIsvjvF57XlaXLucJJy2Jwv0BSMa
cCkRa1gkYEYek74DaSzyXqDSYVO / RPKFTFRQNeUbqbD20s3rbVWablFPAQKBgB5y
zUCJJYh2w6qPQzegjhO4wOm9bxMyngL0l + ka0AUuv7VnSx8TyWIytLoX8P90UVJ1
wpTc3ksK5dDV9ot7n7ThJIXv34nehLkkKckNRLd + oro1FsUw + PkkebWsIxb0avL2
EymI9fvGOPhdW6s91 / OO / VAfDpvUDxNEevSkKtujAoGAcMOsXtn / UyT3Lssxgla3
K + DCaFhAQPSUXOmpZwEbQ0yQlksDe4flsam8bEDI5D5iHx1ziSfh583qJl3BEZ5u
VZTEO2YLvT9QRz7pv2qspqj7nzSyBU2BFAajq43 / G1b8FHfVgN + YdVtzVrigfql5
2a + JxOxFfpjnGQ7RfSxSb + Q =
—– END PRIVATE KEY —–

It is time to test run the service using the following command:

$ go run http_server.go

The answer should be:

Go Backend: {HTTPVersion = 1}; serving on localhost: 9191 / hello / sayHello

Now we initiate the service using an HTTP / 1.1 POST request (insecure and secure modes):

$ curl -k -v localhost: 9191 / hello / sayHello -d "Hello Go!"

$ curl -v –cacert ./cert/server.crt localhost: 9191 / hello / sayHello -d "Hello Go!"

Below is the result of the program. The first part is the details of the handshake TLS server and client. The second is the details of the HTTP / 1.1 request and response. At the end – the text message "Hello Go!".

* Trying 127.0.0.1 …
* Connected to localhost (127.0.0.1) port 9191 (# 0)
* ALPN, offering http / 1.1
* Cipher selection: ALL:! EXPORT:! EXPORT40:! EXPORT56:! ANULL:! LOW:! RC4: @STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http / 1.1
* Server certificate:
* subject: C = LK; ST = Western; L = Colombo; O = LDCLAKMAL; OU = Engineering; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
* start date: Apr 20 03:03:58 2019 GMT
* expire date: Apr 19 03:03:58 2020 GMT
* issuer: C = LK; ST = Western; L = Colombo; O = LDCLAKMAL; OU = Engineering; CN = Chanaka Lakmal; emailAddress=ldclakmal@gmail.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
> POST / hello / sayHello HTTP / 1.1
> Host: localhost: 9191
> User-Agent: curl / 7.46.0
> Accept: * / *
> Content-Length: 9
> Content-Type: application / x-www-form-urlencoded
>
* upload completely sent off: 9 out of 9 bytes
<HTTP / 1.1 200 OK
<Date: Sat, 20 Apr 2019 06:56:19 GMT
<Content-Length: 10
<Content-Type: text / plain; charset = utf-8
<
Hello go!
* Connection # 0 to host localhost left intact

HTTP / 1.1 Client

The new code is an example of writing a simple HTTP / 1.1 client on Go. This client sends an HTTP (S) POST request to localhost: 9191 / hello / sayHello with the message “Hello Go!”.

package main
 
import (
"bytes"
"crypto / tls"
"crypto / x509"
fmt
"io / ioutil"
"log"
"net / http"
)
 
func main () {
client: = & http.Client {}
 
// Create a pool with the server certificate since it is not signed
// by a known CA
caCert, err: = ioutil.ReadFile ("./ cert / server.crt")
if err! = nil {
log.Fatalf ("Reading server certificate:% s", err)
}
caCertPool: = x509.NewCertPool ()
caCertPool.AppendCertsFromPEM (caCert)
 
// Create TLS configuration with the certificate of the server
tlsConfig: = & tls.Config {
RootCAs: caCertPool,
}
 
// Use the proper transport in the client
client.Transport = & http.Transport {
TLSClientConfig: tlsConfig,
}
 
// Perform the request
resp, err: = client.Post ("https: // localhost: 9191 / hello / sayHello", "text / plain", bytes.NewBufferString ("Hello Go!"))
if err! = nil {
log.Fatalf ("Failed get:% s", err)
}
defer resp.Body.Close ()
body, err: = ioutil.ReadAll (resp.Body)
if err! = nil {
log.Fatalf ("Failed reading response body:% s", err)
}
fmt.Printf ("Got response% d:% s% s", resp .StatusCode, resp.Proto, string (body))

Since the HTTP (S) service also starts here, you need to provide a certificate and a key. The necessary data is located in the cert directory with the names server.crt and server.key.

In order to start the client, you need to initialize the server. To do this, perform the actions described in the first section, or launch any HTTP (S) service with port 9191 and the / hello / sayHello path.

$ go run http_client.go

The output should be:

Got response 200: HTTP / 1.1 Hello Go!

The client shows the work of the curl command:

$ curl -v –cacert ./cert/server.crt localhost: 9191 / hello / sayHello -d "Hello Go!"

After you finish working with HTTP / 1.1, you should try repeating the same for HTTP / 2.

HTTP / 2 Server

As in the previous section, you first need to create an echo server.

package main
 
import (
fmt
"golang.org/x/net/http2"
"io / ioutil"
"log"
"net / http"
)
 
func main () {
var httpServer = http.Server {
Addr: ": 9191",
}
var http2Server = http2.Server {}
_ = http2.ConfigureServer (& httpServer, & http2Server)
http.HandleFunc ("/ hello / sayHello", echoPayload)
log.Printf ("Go Backend: {HTTPVersion = 2}; serving on https: // localhost: 9191 / hello / sayHello")
log.Fatal (httpServer.ListenAndServeTLS ("./ cert / server.crt", "./cert/server.key"))
}
 
func echoPayload (w http.ResponseWriter, req * http.Request) {
log.Printf ("Request connection:% s, path:% s", req.Proto, req.URL.Path[1:])
defer req.Body.Close ()
contents, err: = ioutil.ReadAll (req.Body)
if err! = nil {
log.Fatalf ("Oops! Failed reading body of the request.  n% s", err)
http.Error (w, err.Error (), 500)
}
fmt.Fprintf (w, "% s  n", string (contents))

In order to test the server, you need to send the command:

$ go run http2_server.go

The answer should be:

Go Backend: {HTTPVersion = 2}; serving on localhost: 9191 / hello / sayHello

Now the server is running, you can initialize it using HTTP / 1.1 or HTTP / 2 requests. HTTP / 1.1 POST commands for testing server operation are listed below:

$ curl -k -v localhost: 9191 / hello / sayHello -d "Hello Go!"

$ curl -v –cacert ./cert/server.crt localhost: 9191 / hello / sayHello -d "Hello Go!"

And the same thing, but with HTTP / 2 POST:

$ curl -k -v –http2 localhost: 9191 / hello / sayHello -d "Hello Go!"

$ curl -v –http2 –cacert ./cert/server.crt localhost: 9191 / hello / sayHello -d "Hello Go!"

Below is an example of server output. The first part is the TLS handshake between the server and the client, the second is the details of the HTTP / 2 request and response.

* Trying 127.0.0.1 …
* Connected to localhost (127.0.0.1) port 9191 (# 0)
* ALPN, offering h2
* ALPN, offering http / 1.1
* Cipher selection: ALL:! EXPORT:! EXPORT40:! EXPORT56:! ANULL:! LOW:! RC4: @STRENGTH
* successfully set certificate verify locations:
* CAfile: src / hello / cert / server.crt
CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: C = LK; ST = Western; L = Colombo; O = LDCLAKMAL; OU = Engineering; CN = localhost; emailAddress=ldclakmal@gmail.com
* start date: Apr 20 05:27:33 2019 GMT
* expire date: Apr 19 05:27:33 2020 GMT
* common name: localhost (matched)
* issuer: C = LK; ST = Western; L = Colombo; O = LDCLAKMAL; OU = Engineering; CN = localhost; emailAddress=ldclakmal@gmail.com
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP / 2 confirmed)
* TCP_NODELAY set
* Copying HTTP / 2 data for connection buffer after upgrade: len = 0
* Using Stream ID: 1 (easy handle 0x10ddf20)
> POST / hello / sayHello HTTP / 1.1
> Host: localhost: 9191
> User-Agent: curl / 7.46.0
> Accept: * / *
> Content-Length: 9
> Content-Type: application / x-www-form-urlencoded
>
* We are completely uploaded and fine
<HTTP / 2.0 200
<content-type: text / plain; charset = utf-8
<content-length: 10
<date: Sat, 20 Apr 2019 06:54:50 GMT
<
Hello go!
* Connection # 0 to host localhost left intact

HTTP / 2 Client

The last part is creating an HTTP / 2 client. Here is the implementation of sending an HTTP (S) POST request for localhost: 9191 / hello / sayHello:

package main
 
import (
"bytes"
"crypto / tls"
"crypto / x509"
fmt
"golang.org/x/net/http2"
"io / ioutil"
"log"
"net / http"
)
 
func main () {
client: = & http.Client {}
 
// Create a pool with the server certificate since it is not signed
// by a known CA
caCert, err: = ioutil.ReadFile ("./ cert / server.crt")
if err! = nil {
log.Fatalf ("Reading server certificate:% s", err)
}
caCertPool: = x509.NewCertPool ()
caCertPool.AppendCertsFromPEM (caCert)
 
// Create TLS configuration with the certificate of the server
tlsConfig: = & tls.Config {
RootCAs: caCertPool,
}
 
// Use the proper transport in the client
client.Transport = & http2.Transport {
TLSClientConfig: tlsConfig,
}
 
// Perform the request
resp, err: = client.Post ("https: // localhost: 9191 / hello / sayHello", "text / plain", bytes.NewBufferString ("Hello Go!"))
if err! = nil {
log.Fatalf ("Failed get:% s", err)
}
defer resp.Body.Close ()
body, err: = ioutil.ReadAll (resp.Body)
if err! = nil {
log.Fatalf ("Failed reading response body:% s", err)
}
fmt.Printf ("Got response% d:% s% s", resp .StatusCode, resp.Proto, string (body))

As before, you need a key and a certificate. They are saved in cert with the names server.crt and server.key.

Client start:

$ go run http2_client.go

And the expected answer:

Got response 200: HTTP / 2 Hello Go!

Details of the client's work:

$ curl -v –http2 –cacert ./cert/server.crt localhost: 9191 / hello / sayHello -d "Hello Go!"

That's all. The job is relatively simple, but it gives you an understanding of how to implement basic network services in Go.

Skillbox recommends:

  • Two-year practical course "I am a web developer PRO".
  • Educational online course "Profession Java developer".
  • Practical annual course "PHP developer from 0 to PRO".

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *