函数库:http - 基本 HTTP 通信
HTTP(Hypertext Transfer Protocol)协议是一种用于在计算机网络中传输超文本文档的应用层协议。HTTP 是 Web 浏览器和 Web 服务器之间通信的基础,它定义了客户端如何向服务器请求数据,以及服务器如何响应这些请求。
HTTP 协议基于客户端/服务器模型,客户端发出请求并等待服务器响应。在请求和响应之间,HTTP 使用一组标准化的请求方法(例如 GET、POST、PUT 和 DELETE)和响应状态码(例如 200 OK、404 Not Found)来交换信息。
HTTP 协议通常使用 TCP/IP 协议作为传输层协议,但也可以使用其他协议,如 UDP 和 SCTP。HTTP 协议通常使用 TCP 端口号 80,但也可以使用其他端口号,如安全 HTTP(HTTPS)协议使用 TCP 端口号 443。
HTTP 协议是 Web 应用程序的基础,许多应用程序、框架和库都使用 HTTP 协议进行通信。
本篇将会介绍 http 的基础库的操作:主要会从如下方面介绍该协议的编程内容:
#
使用 URL 发起 HTTP 请求#
基础:快速发送一个 GET / POST 请求rsp = http.Get("http://example.com")~http.show(rsp)
我们使用上述代码可以直接发送一个 GET 请求。
注意 ~
是 Yaklang 的特殊语法
上述代码执行之后,http.show(rsp)
会直接把数据包的内容打印到屏幕上,因此我们在控制台中,将会看到如下输出
HTTP/1.1 200 OKConnection: closeAccept-Ranges: bytes...X-Cache: HITContent-Length: 1256
<!doctype html><html><head> <title>Example Domain</title> ... ...<body><div> <h1>Example Domain</h1> <p>This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.</p> <p><a href="https://www.iana.org/domains/example">More information...</a></p></div></body></html>
#
进阶:使用一个客户端发送请求实际在使用过程中,我们可能会有一些其他的诉求:
- 使用除了 Get / Post 之外的方法发送请求
- 需要先创建请求,暂时不发送,等时机成熟后再发送
实际上,我们也支持这种形式:
req = http.NewRequest("HEAD", "http://baidu.com")~http.show(req) // show packet
rsp = http.Do(req)~http.show(rsp) // show packet
/*HEAD / HTTP/1.1Host: baidu.com
HTTP/1.1 200 OKConnection: closeAccept-Ranges: bytesCache-Control: max-age=86400Content-Type: text/htmlDate: Mon, 20 Feb 2023 09:24:18 GMTEtag: "51-47cf7e6ee8400"Expires: Tue, 21 Feb 2023 09:24:18 GMTLast-Modified: Tue, 12 Jan 2010 13:48:00 GMTServer: ApacheContent-Length: 0
*/
一般来说,http.NewRequest
和 http.Do
是配套使用的:
http.NewRequest(method, url, opts...) (*YakHttpRequest, error)
是创建一个请求,参数是可变参数,可以任意设置。http.Do(*YakHttpRequest) (*YakHttpResponse, error)
可以发送这个请求,只接受http.NewRequest
中的内容。
#
处理参数和 HTTP 头#
带参数的 HTTP 请求一般来说,我们可以通过 URL 本身携带参数信息来发起请求,但是并不代表这是唯一选项。
在 >= 1.1.8-sp8
的引擎版本中,我们可以通过 http.params({"key": "value"})
或者 http.postparams({"key": "value"})
来为 GET 请求设置参数。
req = http.NewRequest("GET", "https://www.baidu.com", http.params({"key": "value"}))~http.show(req)
/*OUTPUT:
GET /?key=value HTTP/1.1Host: www.baidu.com
*/
我们了解了上述案例之后,我们大概对 Yak HTTP 有了基础理解,实际上控制参数的方法有如下两个。
http.params
的作用范围是参数(Query)在 URL 中的情况http.postparams
的作用范围是 PostData,且他的编码规范是urlencoded
因此,我们把这两个参数放在一起,将会明白他是如何工作的
req = http.NewRequest("GET", "https://www.baidu.com", http.params({"key": "value"}), http.postparams({"key2": "value2"}))~http.show(req)
/*OUTPUT:
GET /?key=value HTTP/1.1Host: www.baidu.comContent-Length: 11
key2=value2*/
#
使用 JSON 通信通过使用 http.json(...)
可以让 yak 的 http 库自动把对象转变成 JSON 格式的内容,并为他设置好合理的 Content-Type
req = http.NewRequest("GET", "https://www.baidu.com", http.json({"key": "value"}))~http.show(req)
/*OUTPUT:
GET / HTTP/1.1Host: www.baidu.comContent-Type: application/jsonContent-Length: 15
{"key":"value"}*/
这个例子非常直观的解释了参数是如何工作的。
note
一般不建议 http.postparams
和 http.json
进行混用,会造成无法预料的后果,属于未定义的行为。
#
为 HTTP 请求设置 User-Agent一般来说,设置 User-Agent 是一个非常常见的操作,在 Yak 中,我们也可以很好支持这个操作:http.useragent
或 http.ua
都可以完成操作。
req = http.NewRequest("GET", "https://www.baidu.com", http.useragent("Yaklang-User-Agent/1.1"))~http.show(req)
/*OUTPUT:
GET / HTTP/1.1Host: www.baidu.comUser-Agent: Yaklang-User-Agent/1.1
*/
当然,我们在一些特殊场景中经常会使用一个虚假的或者随机的 User-Agent
,通过 http.fakeua()
我们可以随机生成一个 User-Agent 来发送数据包。
req = http.NewRequest("GET", "https://www.baidu.com", http.fakeua())~http.show(req)
/*OUTPUT:
GET / HTTP/1.1Host: www.baidu.comUser-Agent: ELinks/0.12~pre5-4 ...
-------------------------W3C_Validator/1.305.2.12 libwww-perl/5.64ELinks/0.12~pre5-4BlackBerry9700/5.0.0.351 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/123Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36...*/
#
为 HTTP 请求设置任意 HeaderUser-Agent 当然只是一种特殊情况,一般来说,我们还可以设置更多复杂的情况,通过 http.header
可以快速设置。
req = http.NewRequest( "GET", "https://www.baidu.com", http.header("User-Spec-Header", "AwesomeYaklang!"))~http.show(req)
/*OUTPUT:
GET / HTTP/1.1Host: www.baidu.comUser-Spec-Header: AwesomeYaklang!
*/
#
设置 Cookie当然,和上面接口高度类似地,我们可以设置通过类似的操作去设置 Cookie:http.cookie(any)
req = http.NewRequest( "GET", "https://www.baidu.com", http.header("User-Spec-Header", "AwesomeYaklang!"), http.cookie("abc=123"))~http.show(req)
/*OUTPUT:
GET / HTTP/1.1Host: www.baidu.comCookie: abc=123User-Spec-Header: AwesomeYaklang!
*/
#
HTTP 网络控制#
控制 HTTP 超时限制HTTP 超时限制是指客户端在向服务器发送 HTTP 请求时,如果在规定时间内没有收到服务器的响应,就会认为该请求已超时。这个时间限制称为超时时间。
HTTP 超时限制的目的是防止客户端无限期地等待服务器响应,从而浪费客户端的资源。如果客户端等待时间过长,可能会导致用户体验不佳,甚至影响到整个应用程序的性能。因此,设置适当的超时时间非常重要。
在 http
这个库中,默认的超时时间为:15 seconds
。这个默认时间和一般的客户端不太一样,一般客户端的默认超时是 30s。我们通过 http.timeout(float)
直接设置一个超时参数。
我们可以用一个例子来观察这个参数的作用:
rsp = http.Get("http://www.example.com", http.timeout(0.3))~
/*OUTPUT:
Panic Stack:File "/var/folders/0f/ypm71yhs1jdg_nrgs_8_j3180000gn/T/yaki-code-1991723086.yak", in global code--> 1 rsp = http.Get("http://www.example.com", http.timeout(0.3))~
YakVM Panic: native func `http.Get` call error: Get "http://www.example.com": context deadline exceeded (Client.Timeout exceeded while awaiting headers)*/
在这个案例中,我们设置了一个 0.3 秒的超时,当这个超时限制生效的时候,就会触发错误。
当然,我们可以编写一个不用 wavycalling
的版本:
rsp, err = http.Get("http://www.example.com", http.timeout(0.3))if err != nil { log.error(`Get http://www.example.com: %v`, err) return}
/*OUTPUT:
[ERRO] 2023-02-20 17:13:52 +0800 [yaki-code-1149036560] Get http://www.example.com: Get "http://www.example.com": context deadline exceeded (Client.Timeout exceeded while awaiting headers)*/
#
为 HTTP 请求设置代理除了控制超时之外,yak http 也支持用户单独设置一个代理,我们可以使用著名的 ifconfig.me
来验证我们的代理设置成功没有。
rsp = http.Get("http://ifconfig.me", http.timeout(5), http.proxy("http://127.0.0.1:7890"))~http.show(rsp)
/*OUTPUT:
HTTP/1.1 200 OKConnection: closeAccess-Control-Allow-Origin: *Content-Type: text/plain; charset=utf-8Date: Mon, 20 Feb 2023 09:15:57 GMTServer: istio-envoyStrict-Transport-Security: max-age=2592000; includeSubDomainsVia: 1.1 googleX-Envoy-Upstream-Service-Time: 1Content-Length: 12
45.**.***.217*/
#
重定向设置支持在 http 包中,我们默认开启了重定向,但是有时候我们往往需要禁用重定向控制。
rsp = http.Get("https://baidu.com/", http.timeout(5), http.noredirect())~
使用 http.noredirect()
可以直接控制 HTTP 请求是否被重定向。
#
处理 HTTP 响应信息#
获取 HTTP 响应状态码url = "https://www.example.com"rsp = http.Get(url)~
statusCode = rsp.StatusCode // 200printf(f`Request to ${url}Response StatusCode: ${statusCode}
`)
/*OUTPUT:
Request to https://www.example.comResponse StatusCode: 200
*/
我们通过 rsp.StatusCode
可以直接获取到这个请求的状态码。这个字段源自于 Golang 的对象:*http.Request
。
字符串模版
在上面的例子中,我们用到了 Yaklang 的字符串的 f-string
字符串模版特性。
大家可以在 字符串:模版语法 f-string 这里复习这个特性。熟练应用这个特性,它会让你的编程更加流畅。
#
获取 HTTP 响应数据(Body)和上述内容差不多,我们可以通过直接操作 rsp
(*YakHttpResponse) 来获取响应的信息
#
获取数据包的响应数据这个功能和绝大多数 HTTP 库都有不同,我们可以允许用户获取一个请求的具体报文信息:
url = "https://www.example.com"rsp = http.Get(url)~dump([]byte(rsp.Data()))
/*([]uint8) (len=1256 cap=1280) { 00000000 3c 21 64 6f 63 74 79 70 65 20 68 74 6d 6c 3e 0a |<!doctype html>.| 00000010 3c 68 74 6d 6c 3e 0a 3c 68 65 61 64 3e 0a 20 20 |<html>.<head>. | 00000020 20 20 3c 74 69 74 6c 65 3e 45 78 61 6d 70 6c 65 | <title>Example| ... ... ... 000004a0 6f 72 67 2f 64 6f 6d 61 69 6e 73 2f 65 78 61 6d |org/domains/exam| 000004b0 70 6c 65 22 3e 4d 6f 72 65 20 69 6e 66 6f 72 6d |ple">More inform| 000004c0 61 74 69 6f 6e 2e 2e 2e 3c 2f 61 3e 3c 2f 70 3e |ation...</a></p>| 000004d0 0a 3c 2f 64 69 76 3e 0a 3c 2f 62 6f 64 79 3e 0a |.</div>.</body>.| 000004e0 3c 2f 68 74 6d 6c 3e 0a |</html>.|}*/
输出内容展示的步骤
dump(...)
可以尽力保留变量或者值的类型和数据进行美化展示,能帮助用户快速了解变量类型和数据dump()
函数对[]byte
的展示自带了hexdump
的格式,方便用户观察不可见或不可打印字符[]byte(...)
是类型转换,我们通过把 string 转换为 bytes,从而让 dump 函数更好展示 hexdump 形式
#
获取数据包的原始数据这个功能和绝大多数 HTTP 库都有不同,我们可以允许用户获取一个请求的具体报文信息:
url = "https://www.example.com"rsp = http.Get(url)~dump([]byte(rsp.Raw()))
/*([]uint8) (len=1626 cap=1792) { 00000000 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.| 00000010 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f |.Connection: clo| 00000020 73 65 0d 0a 41 63 63 65 70 74 2d 52 61 6e 67 65 |se..Accept-Range| 00000030 73 3a 20 62 79 74 65 73 0d 0a 41 67 65 3a 20 32 |s: bytes..Age: 2| 00000040 34 33 30 36 32 0d 0a 43 61 63 68 65 2d 43 6f 6e |43062..Cache-Con| 00000050 74 72 6f 6c 3a 20 6d 61 78 2d 61 67 65 3d 36 30 |trol: max-age=60| 00000060 34 38 30 30 0d 0a 43 6f 6e 74 65 6e 74 2d 54 79 |4800..Content-Ty| 00000070 70 6... ... 00000610 61 2e 6f 72 67 2f 64 6f 6d 61 69 6e 73 2f 65 78 |a.org/domains/ex| 00000620 61 6d 70 6c 65 22 3e 4d 6f 72 65 20 69 6e 66 6f |ample">More info| 00000630 72 6d 61 74 69 6f 6e 2e 2e 2e 3c 2f 61 3e 3c 2f |rmation...</a></| 00000640 70 3e 0a 3c 2f 64 69 76 3e 0a 3c 2f 62 6f 64 79 |p>.</div>.</body| 00000650 3e 0a 3c 2f 68 74 6d 6c 3e 0a |>.</html>.|}*/
#
发送畸形数据包如果想要发送任意的畸形数据包,我们依赖 http
这个库无法做到,用户需要学习 poc
与 fuzz
模糊测试函数库支持。