本文对应源码:https://github.com/killlowkey/parse-curl
cURL 是什么
维基百科:“cURL是一个命令行工具,用于使用URL语法获取或发送数据”,简而言之,可以使用 cURL 发送 http/https 请求。
使用 cURL
在进行实现之前,我们需要对 cURL 语法有一个大致的了解,当然有使用 cURL 的读者可以跳过该部分。下面来看一个简单的 cURL 例子,该命令向 https://www.baidu.com 发送一个 GET 请求,获取该响应数据。
1
| curl https://www.baidu.com
|
cURL 还提供一系列的参数,比如通过 -X、–request 参数来指定请求方法,-H 参数指定请求头,-d 参数指定请求参数,–data-row 指定 body 等等。之后我们得到一个复杂的 cURL 例子,如下所示,该命令携带一个json数据向 http://localhost:6000/log 发送一个 POST 请求。
1 2 3 4 5 6
| curl --location --request POST 'http://localhost:6000/log' \ --header 'Content-Type: application/json' \ --data-raw '{ "serviceName": "Gateway-Service", "content": "test" }'
|
小试牛刀
现在进入正题,有了前面的知识铺垫,相信会事半功倍。编写代码之前,先来讲述一下实现思路,cURL 是一个类似与 key/value 格式数据,也就是说通过 -H 指定请求头参数,后面跟着请求头数据,所以这里采用状态机方式实现会更好。
一个完整的请求由请求行、请求头和body进行组成,下面我们定义了一个 Request struct 来表示请求,解析之后通过该 struct 进行表示。
1 2 3 4 5 6 7 8
| type Header map[string]string
type Request struct { Method string `json:"method"` Url string `json:"url"` Header Header `json:"header"` Body string `json:"body"` }
|
解析之前需要对传入的 curl 参数进行一个判断,如果该参数不是一个 curl 命令,那么无需继续解析了。判断方法也很简单,如果该参数是 curl 开头(注意 curl 后还有个空格),那么基本可以认定该参数是一个 curl 命令。
1 2 3
| if strings.Index(curl, "curl ") != 0 { return nil, false }
|
因为 curl 是一个 shell 命令,接下来使用 go-shellwords 库对 curl 参数进行解析,得到一个 string 数组。
1 2 3 4
| args, err := shellwords.Parse(curl) if err != nil { return nil, false }
|
之后调用 rewrite 方法对 args 数组进行二次清洗,该步骤的目的是为了去除数组中的\n 元素。因为 curl 还有一个特殊的参数,使用-X 参数来指定请求方法,比如说 -XPUT,该参数指定了一个 PUT 请求,所以这里需要将 -XPUT 这种参数进行拆分,得到 -X 与 PUT 两个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| func Parse(curl string) (*Request, bool) { .... args = rewrite(args) request := &Request{Method: "GET", Header: Header{}} state := "" .... }
func rewrite(args []string) []string { res := make([]string, 0)
for _, arg := range args { arg = strings.TrimSpace(arg)
if arg == "\n" { continue }
if strings.Contains(arg, "\n") { arg = strings.ReplaceAll(arg, "\n", "") }
if strings.Index(arg, "-X") == 0 { res = append(res, arg[0:2]) res = append(res, arg[2:]) } else { res = append(res, arg) } }
return res }
|
实现状态机
状态机是实现解析 cURL 核心部分,前面我们对 cURL 参数进行分割与清洗。
实现状态机之前我们需要对 cURL 一些参数进行了解,比如说通过 -A 与 –user–agent 参数可以指定 user-agent。此时就可以给状态机赋值为 user-agent 然后就跳出该 switch,等到遍历到下一个参数的时候,就进入 switch 中的最后一个 case,在该case 里面,会对状态机的状态进行检测,提取出相应的值,最后清除状态的状态。
解析 -H 与 –header 参数时,需要进行拆分操作。比如说 -H 'Accept-Encoding: gzip, deflate, sdch',此时就需要获取请求头与值得到 Accept-Encoding 与 gzip, deflate, sdch,最后添加到 request struct 的 header 中。请求的类型为 application/x-www-form-urlencoded 时,需要使用 & 对请求参数进行拼接,倘若是 application/json,那么直接赋值即可。解析 -u 与 –user 参数时,需要对 arg 参数进行一个 base64 编码操作,然后向请求添加一个 Authorization 头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| func Parse(curl string) (*Request, bool) { .... for _, arg := range args { switch true { case isUrl(arg): request.Url = arg break
case arg == "-A" || arg == "--user-agent": state = "user-agent" break
case arg == "-H" || arg == "--header": state = "header" break
case arg == "-d" || arg == "--data" || arg == "--data-ascii" || arg == "--data-raw": state = "data" break
case arg == "-u" || arg == "--user": state = "user" break
case arg == "-I" || arg == "--head": request.Method = "HEAD" break
case arg == "-X" || arg == "--request": state = "method" break
case arg == "-b" || arg == "--cookie": state = "cookie" break
case len(arg) > 0: switch state { case "header": fields := parseField(arg) request.Header[fields[0]] = strings.TrimSpace(fields[1]) state = "" break
case "user-agent": request.Header["User-Agent"] = arg state = "" break
case "data": if request.Method == "GET" || request.Method == "HEAD" { request.Method = "POST" }
if !hasContentType(*request) { request.Header["Content-Type"] = "application/x-www-form-urlencoded" }
if len(request.Body) == 0 { request.Body = arg } else { request.Body = request.Body + "&" + arg }
state = "" break
case "user": request.Header["Authorization"] = "Basic " + base64.StdEncoding.EncodeToString([]byte(arg)) state = "" break
case "method": request.Method = arg state = "" break
case "cookie": request.Header["Cookie"] = arg state = "" break
default: break } }
} .... }
|
略有小成
创建一个 example,使用 Parse 方法传入一个 curl 命令,得到一个 json 对象。
1 2 3 4 5 6 7 8 9 10 11
| package main
import ( "fmt" parseCurl "parse-curl" )
func main() { request, _ := parseCurl.Parse("curl 'http://google.com/' \\\n -H 'Accept-Encoding: gzip, deflate, sdch' \\\n -H 'Accept-Language: en-US,en;q=0.8,da;q=0.6' \\\n -H 'Upgrade-Insecure-Requests: 1' \\\n -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36' \\\n -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8' \\\n -H 'Connection: keep-alive' \\\n --compressed\n") fmt.Println(request.ToJson(true)) }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| { "method": "GET", "url": "http://google.com/", "header": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, sdch", "Accept-Language": "en-US,en;q=0.8,da;q=0.6", "Connection": "keep-alive", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36" }, "body": "" }
|
参考