Writing HTTP / 1.1 & HTTP / 2 Client and Server in Golang

Original author: Chanaka Lakmal
  • Transfer


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

Skillbox recommends: The Python Developer from scratch hands-on course .

We remind you: for all readers of “Habr” - a discount of 10,000 rubles when registering for any Skillbox course using the “Habr” promo code.

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 the echo logic, analyzing incoming traffic and reacting 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 a 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

----- the BEGIN CERTIFICATE -----
MIID + + zCCAuOgAwIBAgIJAPsvGCCAC2i MA0GCSqGSIb3DQEBCwUAMIGTMQswCQYD
VQQGEwJMSzEQMA4GA1UECAwHV2VzdGVybjEQMA4GA1UEBwwHQ29sb21ibzESMBAG
A1UECgwJTERDTEFLTUFMMRQwEgYDVQQLDAtFbmdpbmVlcmluZzESMBAGA1UEAwwJ
bG9jYWxob3N0MSIwIAYJKoZIhvcNAQkBFhNsZGNsYWttYWxAZ21haWwuY29tMB4X
DTE5MDQyMDA1MjczM1oXDTIwMDQxOTA1MjczM1owgZMxCzAJBgNVBAYTAkxLMRAw
DgYDVQQIDAdXZXN0ZXJuMRAwDgYDVQQHDAdDb2xvbWJvMRIwEAYDVQQKDAlMRENM
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
+ q3A2cBhi3NGo6Ho1s2rywQyqiq8 szrq1eWKhYI2zf4iUVpwvq89OynVGIp0atng
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 + Q + = JxOxFfpjnGQ7RfSxSb
----- END PRIVATE KEY -----

It's time to test run the service using the following command:

$ go run http_server.go

The answer should be like this:

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

Now we initiate the service using the HTTP / 1.1 POST request (unsafe and safe modes):

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

Below is the result of the program. The first part is the handshake details of the TLS server and client. The second is the details of the HTTP / 1.1 request and response. At the end is 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 placed 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, we perform the actions described in the first section, or we start any HTTP (S) service with port 9191 and the path / hello / sayHello.

$ go run http_client.go

The output should be like this:

Got response 200: HTTP/1.1 Hello Go!

The client shows the operation of the curl command:

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

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

HTTP / 2 Server


As in the previous section, you must first 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 check the server, you need to send the command:

$ go run http2_server.go

The answer should be like this:

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

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

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

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

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

Below is an example of server operation output. The first part is a 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 in stream buffer to 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 to work. They are stored in cert with the names server.crt and server.key.

Client launch:

$ go run http2_client.go

And the expected answer:

Got response 200: HTTP/2 Hello Go!

Detailing the client:

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

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

Skillbox recommends:


Also popular now: