HTTP Demo
client.go
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
"time"
)
func Test(client *http.Client) error {
body := "i am client"
req, err := http.NewRequest(http.MethodPost, "http://fzz:joy@localhost:64896/test;word=222?id=1#frag", strings.NewReader(body))
if err != nil {
_ = fmt.Errorf("http new request fail, %s", err.Error())
return fmt.Errorf("pub task err, build request failed %w", err)
}
req.Header["1header"] = []string{"head1", "head2"}
req.Header["2header"] = []string{"head3", "head4"}
resp, err := client.Do(req)
if err != nil {
fmt.Println("error:", err)
return nil
}
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(respBody))
return nil
}
func main() {
// init client
client := http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
for {
time.Sleep(3 * time.Second)
err := Test(&client)
if err != nil {
_ = fmt.Errorf("err is %s", err)
}
}
}
server.go
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func sayHelloName(w http.ResponseWriter, r *http.Request) {
// 提取用户名 密码
username, password, ok := r.BasicAuth()
if ok {
fmt.Println("req username:", username)
fmt.Println("req password:", password)
}
// 获取原始的query参数
fmt.Println("req raw query:", r.URL.RawQuery)
// 获取method
fmt.Println("req method:", r.Method)
// 获取host
fmt.Println("req host:", r.Host)
// 获取访问的路径
fmt.Println("req path:", r.URL.Path)
// 获取Query参数
var arg string
values := r.URL.Query()
arg=values.Get("id")
fmt.Println("req query params -> id:", arg)
// 获取Header
fmt.Println("req.Header:", r.Header)
// 获取body信息
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println("req.Body:", string(reqBody))
_, _ = fmt.Fprintf(w, "{\"Hello\": \"Wrold\"}") //这个写入到w的是输出到客户端的
fmt.Println("***********************************************")
}
func main() {
// init server
http.HandleFunc("/", sayHelloName) // 设置访问的路由
err := http.ListenAndServe(":64896", nil) //设置监听的端口
if err != nil {
fmt.Errorf("http server error: %s", err)
return
}
}
HTTPS
⚠️ 从golang 1.15开始废弃 CommonName,因此推荐使用 SAN 证书。 如果想兼容之前的方式,需要设置环境变量 GODEBUG 为 x509ignoreCN=0。
“x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0”.
生成CA根证书
# 生成根CA证书的私钥 - Generate the private key of the root CA
$ openssl genrsa -out ca.key 2048
Generating RSA private key, 2048 bit long modulus
..............................................................................................................................+++
................+++
e is 65537 (0x10001)
# 生成自签名根 CA 证书 - Generate the self-signed root CA certificate
$ openssl req -new -x509 -days 3650 -key ca.key -out ca.pem
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:cn
State or Province Name (full name) []:shanghai
Locality Name (eg, city) []:shanghai
Organization Name (eg, company) []:golang
Organizational Unit Name (eg, section) []:https
Common Name (eg, fully qualified host name) []:localhost
Email Address []:
$ ls
ca.key ca.pem
配置openssl
默认安装的 OpenSSL 配置文件路径如下:
- linux系统在 : /etc/pki/tls/openssl.cnf
- Mac系统在: /System/Library/OpenSSL/openssl.cnf
- Windows:安装目录下 openssl.cfg,比如 D:\Program Files\OpenSSL-Win64\bin\openssl.cfg
拷贝配置文件到当前目录下,然后修改如下:
- 找到 [ CA_default ],打开 copy_extensions = copy
- 找到[ req ],打开 req_extensions = v3_req # The extensions to add to a certificate request
- 找到[ v3_req ],添加 subjectAltName = @alt_names
- 添加新的标签 [ alt_names ] , 和标签字段
这里填入需要加入到 Subject Alternative Names 段落中的域名名称,可以写入多个。[ alt_names ] DNS.1 = localhost DNS.2 = *.custer.fun
生成服务端证书 - Mac环境
# 生成服务端私钥
$ openssl genpkey -algorithm RSA -out server.key
..................................+++
.............................................+++
# 根据私钥和CA证书信息生成证书请求文件:server.csr
# 完成后,会生成一个名为 server.pem 的证书文件,同时还创建了一个 ca.srl 文件。
# 这里要注意的几个参数值要和生成CA证书所用的是一样的
$ openssl req -new -nodes -key server.key -out server.csr -days 3650 -subj "/C=cn/OU=https/O=golang/CN=localhost" -config ./openssl.cnf -extensions v3_req
# 根据证书请求文件生成服务端证书,并签名认证
$ openssl x509 -req -days 3650 -in server.csr -out server.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
Signature ok
subject=/C=cn/OU=https/O=golang/CN=localhost
Getting CA Private Key
$ ls
ca.key ca.pem ca.srl openssl.cnf server.csr server.key server.pem
- server.csr: 是上面生成的CA证书的请求文件,ca.pem/ca.key是CA证书文件和私钥,用来对server.csr进行签名认证
- server.key: 服务端私钥
- server.pem: 经过CA证书签名后的服务端证书
- ca.srl: 是CA使用的序列号文件,当使用"-CA"选项来签名时,它将会使用某个文件中指定的序列号来唯一标识此次签名后的证书文件。这个序列号文件的内容仅只有一行,这一行的值为16进制的数字,当某个序列号被使用后,该文件中的序列号将自动增加。
生成客户端证书 - 同理服务端
$ openssl genpkey -algorithm RSA -out client.key
...........................+++
.....+++
$ openssl req -new -nodes -key client.key -out client.csr -days 3650 -subj "/C=cn/OU=https/O=golang/CN=localhost" -config ./openssl.cnf -extensions v3_req
$ ls
ca.key ca.pem ca.srl client.csr client.key openssl.cnf server.csr server.key server.pem
$ cat ca.srl
CE137EED81F35C23
$ openssl x509 -req -days 3650 -in client.csr -out client.pem -CA ca.pem -CAkey ca.key -CAcreateserial -extfile ./openssl.cnf -extensions v3_req
Signature ok
subject=/C=cn/OU=https/O=golang/CN=localhost
Getting CA Private Key
# ca证书加签一次,序号+1
$ cat ca.srl
CE137EED81F35C24
$ ls
ca.key ca.pem ca.srl client.csr client.key client.pem openssl.cnf server.csr server.key server.pem
Demo 测试
-
双向认证
-
client
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
clientCertFile := "client.pem"
clientKeyFile := "client.key"
caCertFile := "ca.pem"
var cert tls.Certificate
var err error
if clientCertFile != "" && clientKeyFile != "" {
cert, err = tls.LoadX509KeyPair(clientCertFile, clientKeyFile)
if err != nil {
fmt.Printf("error creating x509 keypair from client cert file %s and client key file %s", clientCertFile, clientKeyFile)
}
}
caCert, err := ioutil.ReadFile(caCertFile)
if err != nil {
fmt.Printf("Error opening cert file %s, Error: %s", caCertFile, err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
},
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://localhost:8869")
if err != nil {
fmt.Println("error:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
}
fmt.Println(string(body))
}
- server
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w,
"Hi, This is an example of https service in golang!")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServeTLS(":8869", "server.pem", "server.key", nil)
}
- 浏览器访问Server
MacOS Chrome信任自签名证书 - curl访问
curl -kv https://localhost:8869
查看证书内容
使用cat指令也能查看,但信息不够全面
$ openssl x509 -in ca.pem -text