Go--发起HTTP请求
一、HTTP请求
根据 HTTP 标准,HTTP 请求可以使用多种请求方法。在日常开发中大多数会用到 5 种请求方法: GET、POST、PUT、PATCH 和 DELETE
方法 | 描述 |
GET | 请求指定的页面信息,并返回实体主体 |
POST | 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改 |
PUT | 从客户端向服务器传送的数据取代指定的文档的内容 |
DELETE | 请求服务器删除指定的页面 |
PATCH | 是对 PUT 方法的补充,用来对已知资源进行局部更新 |
在一次http请求(request)中可携带参数的地方:
部分 | 用途 |
URL部分 | 即以?和&分割的url参数 |
header部分 | 在http头中的字段,比如常用的cookie |
content-body部分 | 请求正文,如果是get请求则为空;如果是post请求,则请求头中的Content-Type字段规定了content-body部分应该怎么被解析 |
二、GET请求
2.1 不带参数的GET请求
package main import (
"fmt"
"io/ioutil"
"net/http"
) func HttpGet(url string) []byte {
//发起请求
res, err := http.Get(url)
if err != nil {
fmt.Println("get请求失败:", err)
}
//延迟关闭(返回response的body尽量关闭,避免内存泄露)
defer res.Body.Close()
// 请求头
fmt.Println(res.Header)
// 请求相应码
fmt.Println(res.Status) //读取body数据
body, err := ioutil.ReadAll(res.Body)
if err != nil {
fmt.Println("读取body失败:", err)
return nil
} return body
} func main() {
UrlPath := "https://xxx/instance"
body := HttpGet(UrlPath)
//打印body,以字符串形式
fmt.Printf(string(body)) }
2.2 带参数的GET请求
2.2.1 静态参数
若参数是固定的,则将1.1中的UrlPath设置为完整的请求即可,例: UrlPath := "https://xxx/instance?name=xxx&age=18"
2.2.2 动态参数
package main import (
"fmt"
"io/ioutil"
"net/http"
) func HttpGetUrl(url string) []byte {
//正常来说,GET请求所有的参数都应该是放在url上,body请求体中是没有数据的
//请求头可能会携带token之类的参数 //新建一个GET请求
request, err := http.NewRequest("GET", url, nil)
if err != nil {
panic(err)
} //请求头部信息
//Set时候,如果原来这一项已存在,后面的就修改已有的
//Add时候,如果原本不存在,则添加,如果已存在,就不做任何修改
//最终服务端获取的应该是token2
request.Header.Set("User-Agent", "自定义浏览器1...")
request.Header.Set("User-Agent", "自定义浏览器2...")
request.Header.Add("Host", "www.xxx.com") //header: map[User-Agent:[自定义浏览器2...]]
request.Header.Add("name", "alnk")
request.Header.Add("name", "alnk2") //header: map[Name:[alnk alnk2] User-Agent:[自定义浏览器2...]]
request.Header.Add("Authorization", "token1...") //token fmt.Println("header: ", request.Header) //url参数
query := request.URL.Query()
query.Add("id", "1")
query.Add("id", "2")
query.Add("name", "wan")
request.URL.RawQuery = query.Encode()
fmt.Println("request.URL: ", request.URL) //request.URL: https://xxx/instance?id=1&id=2&name=wan //发送请求给服务端,实例化一个客户端
client := &http.Client{}
res, err := client.Do(request)
if err != nil {
panic(err)
}
defer res.Body.Close() //服务端返回数据
b, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
return b
} func main() {
UrlPath := "https://xxx/instance"
body := HttpGetUrl(UrlPath)
//打印body,以字符串形式
fmt.Printf(string(body)) }
三、POST请求
3.1 Content-Type类型
Content-Type | 用途 |
application/x-www-form-urlencoded | 默认的方式, 比如 title=test&id=1010 |
multipart/form-data | 如果你的请求里包含多个文件等二进制信息,则form 的 enctyped 需要等于这个值,浏览器生成一个 boundary 用于分割不同的字段 |
application/json | 以json格式传送 |
raw | 纯二进制格式,这需要服务器拿到消息后自己定义解析方式,比如protobuf |
3.2 使用 http.Post() + application/x-www-form-urlencoded
package main import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
) func HttpPost(urlP string) []byte {
//url.Values用来存放body参数,无参数不加
urlValues := url.Values{} //urlValues ---> map[string][]string
urlValues.Add("user", "test")
urlValues.Add("password", "test123")
//编码
reqBody := urlValues.Encode() //最终http报文里面的body是 user=test&password=test123
resp, err := http.Post(urlP, "application/x-www-form-urlencoded", strings.NewReader(reqBody))
if err != nil {
fmt.Println(err)
return nil
} defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body) return body
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPost(urlP)
fmt.Println(string(body))
}
3.3 使用 http.Post() + application/json
package main import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
) func HttpPost(urlP string) []byte {
// 参数
data := make(map[string]interface{})
data["user"] = "test"
data["password"] = "test123" // 序列化
bytesData, _ := json.Marshal(data) resp, err := http.Post(urlP, "application/json", bytes.NewReader(bytesData))
if err != nil {
fmt.Println(err)
return nil
} defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body) return body
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPost(urlP)
fmt.Println(string(body))
}
3.4 http.PostForm()
提交post表单
package main import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
) func HttpPost(urlP string) []byte {
urlValues := url.Values{}
urlValues.Add("user", "test")
urlValues.Add("password", "test123") resp, err := http.PostForm(urlP, urlValues)
//resp, err := http.PostForm(urlP, url.Values{
// "user": {"test"},
// "password": {"test123"},
//})
if err != nil {
fmt.Println(err)
return nil
} defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body) return body
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPost(urlP)
fmt.Println(string(body))
}
四、http.NewRequest()
如2.2.2,复杂的http请求,可以通过http.NewRequest 来创建请求,再用client.Do发送,http.NewRequest支持各种请求方法
get请求如2.2.2
4.1 POST
package main import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
) func HttpPost(urlP string) []byte {
//请求body
urlMap := url.Values{}
urlMap.Add("user", "test")
urlMap.Add("password", "test123") //新建请求
request, err := http.NewRequest("POST", urlP, strings.NewReader(urlMap.Encode()))
if err != nil {
fmt.Println(err)
return nil
}
fmt.Println("request.url: ", request.URL)
fmt.Println("request.method: ", request.Method) //请求头部信息
request.Header.Add("Authorization", "token1...")
//post formdata表单请求
request.Header.Add("Content-Type", "application/x-www-form-urlencoded") ////发送json格式的参数
//data := map[string]interface{}{
// "user": "test",
// "password": 123,
//}
//// 序列化
//bytesData, _ := json.Marshal(data)
//request, _ := http.NewRequest("POST", urlP, strings.NewReader(string(bytesData)))
//请求头设置
//request.Header.Add("Authorization", "token1...") //token
//request.Header.Add("Content-Type", "application/json") //json请求 //实例化一个客户端
client := &http.Client{}
//发送请求到服务端
resp, err := client.Do(request)
if err != nil {
fmt.Println(err)
return nil
} defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body) return body
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPost(urlP)
fmt.Println(string(body))
}
4.2 DELETE
package main import (
"fmt"
"io/ioutil"
"net/http"
) func HttpDelete(urlD string) []byte {
//携带tonken
//通过ID删除
request, err := http.NewRequest("DELETE", urlD, nil)
if err != nil {
panic(err)
} //请求头部信息
request.Header.Add("Authorization", "token1...") //token //url参数
query := request.URL.Query()
query.Add("id", "1")
query.Add("id", "2")
query.Add("id", "3")
request.URL.RawQuery = query.Encode() //发送请求给服务端
client := &http.Client{}
res, err := client.Do(request)
if err != nil {
panic(err)
}
defer res.Body.Close() //服务端返回数据
b, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
} return b
} func main() {
var urlD = "https://www.xxx.com/json"
body := HttpDelete(urlD)
fmt.Println(string(body))
}
4.3 PUT
package main import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
) func HttpPut(urlP string) []byte {
//通过ID修改用户数据
//数据格式化
data := map[string]interface{}{
"name": "wang",
"age": 18,
}
dataStr, err := json.Marshal(data)
if err != nil {
panic(err)
} //创建一个新的请求
request, err := http.NewRequest("PUT", urlP, strings.NewReader(string(dataStr)))
if err != nil {
panic(err)
} //请求头设置
request.Header.Add("Authorization", "token1...") //token
request.Header.Add("Content-Type", "application/json") //json请求 //url参数
query := request.URL.Query()
query.Add("id", "1")
request.URL.RawQuery = query.Encode() //发送请求给服务端
client := &http.Client{}
res, err := client.Do(request)
if err != nil {
panic(err)
}
defer res.Body.Close() //服务端返回数据
b, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
return b
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPut(urlP)
fmt.Println(string(body))
}
4.4 PATCH
package main import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
) func HttpPatch(urlP string) []byte { data := map[string]interface{}{
"user": "test",
"password": 123,
}
dataStr, err := json.Marshal(data)
if err != nil {
panic(err)
} //创建一个新的请求
request, err := http.NewRequest("PATCH", urlP, strings.NewReader(string(dataStr)))
if err != nil {
panic(err)
} //请求头设置
request.Header.Add("Authorization", "token1...") //token
request.Header.Add("Content-Type", "application/json") //json请求 //发送请求给服务端
client := &http.Client{}
res, err := client.Do(request)
if err != nil {
panic(err)
}
defer res.Body.Close() //服务端返回数据
b, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
return b
} func main() {
var urlP = "https://www.xxx.com/json"
body := HttpPatch(urlP)
fmt.Println(string(body))
}
五、处理响应
不管用何种方式请求,最后都会得到 response,err ,可将其直接打印出来,或者做进一步操作
5.1 直接打印
package main import (
"encoding/json"
"fmt"
"io"
"net/http"
) func main() {
//发起一个get请求
res, _ := http.Get("https://www.baidu.com")
fmt.Println(res.StatusCode) //获取状态码
fmt.Println(res.Status) //获取状态码及英文名称
fmt.Println(res.Header) //获取响应头
body, _ := io.ReadAll(res.Body) //读取响应body,返回为[]byte,数据基本都存储在body里
fmt.Printf(string(body)) //转换成json字符串进行打印 // 将响应数据解析为JSON格式
var jsonData map[string]interface{}
err := json.Unmarshal(body, &jsonData)
if err != nil {
fmt.Println("解析JSON数据失败:", err)
return
} // 输出JSON数据
fmt.Println(jsonData)
}
5.2 对返回数据做进一步操作(解析json类型的返回结果)
很多时候,请求一个接口,是需要返回数据(返回数据类型多为json)中的某些字段的数据,引用或变更,直接转换为字符串打印的话,就变成了一个整体,不便于数据拆解,故此对于返回数据还需另外处理
5.2.1 定义结构体
对于返回数据,可定义一个结构体(需与接口返回body的数据结构一致,不然会报错),然后使用 json.Unmarshal 函数将返回的json字符串解析为结构体类型的值,就可引用该结构体的字段来访问解析后的数据,如下:
package main import (
"encoding/json"
"fmt"
"io"
"net/http"
) // RequestData 定义请求接收的数据的结构体
type RequestData struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
City string `json:"city"`
} func main() {
// 发起GET请求
resp, err := http.Get("https://www.xxx.com/data")
if err != nil {
fmt.Println("发起请求时发生错误:", err)
return
}
defer resp.Body.Close() // 读取响应体的内容
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("读取响应体时发生错误:", err)
return
} // 解析响应体中的JSON数据到结构体
var data RequestData
err = json.Unmarshal(body, &data)
if err != nil {
fmt.Println("解析JSON数据时发生错误:", err)
return
} // 输出解析后的数据,或后续引用
fmt.Println("ID:", data.ID)
fmt.Println("Name:", data.Name)
fmt.Println("Age:", data.Age)
fmt.Println("City:", data.City) //循环
//data1 := make(map[string]RequestData)
//err = json.Unmarshal(body, &data)
//if err != nil {
// fmt.Println("解析JSON数据时发生错误:", err)
// return
//}
//for _, v := range data1 {
// fmt.Println(v.ID)
// fmt.Println(v.Age)
//}
}
5.2.2 gjson
对于一般场景,如5.1.1,定义结构体即可,但有时返回body结构太复杂,定义容易出错;或者返回数据太多,而真正需要的数据,可能只有几个字段,这时候定义个结构体去拆解所有的返回数据,就有点费时费力,且得不偿失了
故针对以上情景,可以使用gjson来处理返回body数据,方便快捷,不易出错
下载gjson: go get -u github.com/tidwall/gjson
使用:
传入 JSON 串和要读取的键路径,路径使用点号语法,如 "name.last "或 "age"。一旦找到值,就会立即返回。
路径是一系列用点分隔的键。键可以包含特殊的通配符 "*"和"? 要访问数组值,使用索引作为键。要获取数组中的元素个数或访问子路径,请使用 "#"字符。点和通配符可以用'\'转义。
例(详细用法请访问:https://www.cnblogs.com/Xinenhui/p/17879819.html):
func main() {
//发送请求
request, _ := http.NewRequest("POST", urlP, bytes.NewBuffer(bytesData))
//实例化一个客户端
client := &http.Client{}
//发送请求到服务端
resp, _ := client.Do(request)
defer resp.Body.Close()
//读取响应数据
body, _ := io.ReadAll(resp.Body)
//处理数据
results := gjson.Get(string(body), "friends") //string(body)将返回数据转换成json字符串,初始数据为friends数组
results.ForEach(func(_, result gjson.Result) bool { //遍历friends数组,一般不需要键k,故用_忽略
first1 := gjson.Get(result.Raw, "first") //默认返回的是gjson.Result类型,如果是直接打印,可直接使用,若是需进一步操作,建立转换成对应类型
first2 := gjson.Get(result.Raw, "first").String() //从 jsonStr 中获取 "name" 字段的值,然后通过调用 .String() 方法,将获取到的值转换为字符串类型,如果 "name" 字段的值不是字符串类型,它会被转换为字符串
first3 := gjson.Get(result.Raw, "first").Str //也是从 jsonStr 中获取 "name" 字段的值,但是是直接获取该字段的值,没有进行类型转换,如果"name"字段的值不是字符串类型,则将保持原来的类型
//所以如果确定 "name" 字段的值一定是字符串类型,那么使用 .Str 是更简洁的方式。如果不确定 "name" 字段的值是什么类型,或者想要确保它是一个字符串类型,那么使用 .String() 进行显式转换是更安全的选择。 fmt.Println(first1, first2, first3)
return true
})
//假设返回数据为:
//{
// "name": {"first": "Tom", "last": "Anderson"},
// "age":37,
// "children": ["Sara","Alex","Jack"],
//"friends": [
// {"first": "James", "last": "Murphy"},
// {"first": "Roger", "last": "Craig"}
// ]
//} //对应键路径的值
//"name.last" >> "Anderson"
//"age" >> 37
//"children" >> ["Sara","Alex","Jack"]
//"children.#" >> 3
//"children.1" >> "Alex"
//"child*.2" >> "Jack"
//"c?ildren.0" >> "Sara"
//"friends.#.first" >> ["James","Roger"]
}
六、补充
6.1 ioutil.ReadAll 已弃用
从go 1.16及之后的版本,io/ioutil就已经被正式弃用。为了兼容性考虑,依旧可以正常调用这个包,只不过当调用ioutil里面的方法,最后都是跳转到了io以及os包。
因为ioutil.ReadAll 通过 slice 将数据流读到内存中,slice 会动态扩容,对于大文件的读取会导致内存不足,被 OOM kill,并且频繁的动态扩容也会导致时间消耗增加。
故:返回的数据量小时,可继续使用或用io.ReadAll,返回的数据量大或者不清楚数据大小时,使用io.Copy():直接从 src 读取数据,并写入到 dst,不会一次性读取全部数据,也不会频繁进行切片扩容。
详细情况请自行百度
package main import (
"fmt"
"io"
"net/http"
"os"
) func main() {
res, _ := http.Get("https://www.baidu.com")
//body, _ := ioutil.ReadAll(res.Body)
//body, _ := io.ReadAll(res.Body)
body, _ := io.Copy(os.Stdout, res.Body)
fmt.Printf(string(body))
}
6.2 较复杂的json参数
要传递的参数形式:
{
"content": "test",
"notifyParams": [
{
"type": "jobNos",
"values": [
"80xxxxxx",
"W9xxxxxx",
"O00xxxxxx"
]
}
]
}
请求样例:
package main import (
"bytes"
"encoding/json"
"fmt"
"net/http"
) type ttNotice struct {
Content string `json:"content"`
NotifyParams []notifyParams `json:"notifyParams"`
}
type notifyParams struct {
Type string `json:"type"`
Values []string `json:"values"`
} func main() {
url := "http://xxx.com" notifyList := make([]notifyParams, 0) //实例化一个list:[{type: "",Values: ["",""]},{...}]
var arrs notifyParams
arrs.Type = "jobNos"
for _, v := range arr { //arr为带有多个id的结构体
arrs.Values = append(arrs.Values, v.SolverId)
}
//去除重复的id
var arrLen int = len(arrs.Values) - 1
for ; arrLen > 0; arrLen-- {
//拿最后项与前面项的各项逐个(自后向前)进行比较
for j := arrLen - 1; j >= 0; j-- {
if arrs.Values[arrLen] == arrs.Values[j] { //例:len(arrs.Values)=5,则下标为0~4,arrs.Values[4] == arrs.Values[3]
arrs.Values = append(arrs.Values[:arrLen], arrs.Values[arrLen+1:]...) //切片,arrs.Values[:4],arrs.Values[5:]
break
}
}
} notifyList = append(notifyList, arrs)
notice := ttNotice{
Content: "test",
NotifyParams: notifyList,
}
//序列化
s1, err := json.Marshal(notice)
if err != nil {
fmt.Println(err)
}
//发送请求
_, err = http.Post(url, "application/json", bytes.NewReader(s1))
if err != nil {
fmt.Println(err)
}
}
6.2.1 切片
package main import "encoding/json" //传参格式
type str struct {
Name string `json:"name"`
Id []string `json:"id"`
} func main() {
s1 := []string{"2x101"}
s2 := []string{"w41e2e", "52s5sf"} //传参
data1 := str{
Name: "wang",
Id: s1,
}
data2 := str{
Name: "wang",
Id: s2[:1],
}
bytesData, _ := json.Marshal(datax) var s4 []string
s4 = append(s4, "1024565V")
s4 = append(s4, "1554200V")
var str1 str
str1.Name = "wang"
str1.Id = s4
bytesData, _ = json.Marshal(str1) //s3 := map[string]interface{}{
// "name": "wang",
// "id": []string{"sf451x"},
//}
//bytesData, _ = json.Marshal(s3)
}
Go--发起HTTP请求的更多相关文章
- Ajax_02之XHR发起异步请求
1.Ajax: AJAX:Asynchronous Javascript And Xml,异步的JS和XML: 同步请求:地址栏输入URL.链接跳转.表单提交-- 异步请求:使用Ajax发起,底层使用 ...
- python urllib2 发起http请求post
使用urllib2发起post请求 def GetCsspToken(): data = json.dumps({"userName":"wenbin", &q ...
- libcurl发起post请求时间延迟问题。except为空即可
最近在做团购酒店APP分享到qzone功能,使用libcurl访问qzone的分享cgi接口,酒店分享信息以POST方式传输,在测试的时候发现分享接口平均有2s的延迟,这延迟也太大了吧,于是乎问了空间 ...
- 发起post请求
string postUrl = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo"; //string req ...
- 关于java发起http请求
我们到底能走多远系列(41) 扯淡: 好久没总结点东西了,技术上没什么总结,感觉做事空牢牢的.最近也比较疲惫. 分享些东西,造福全人类~ 主题: 1,java模拟发起一个http请求 使用HttpUR ...
- [Java] 两种发起POST请求方法,并接收返回的响应内容的处理方式
1.利用apache提供的commons-httpclient-3.0.jar包 代码如下: /** * 利用HttpClient发起POST请求,并接收返回的响应内容 * * @param url ...
- android4.0 HttpClient 以后不能在主线程发起网络请求
android4.0以后不能在主线程发起网络请求,该异步网络请求. new Thread(new Runnable() { @Override public void run() { // TODO ...
- php 使用curl发起https请求
今天一个同事反映,使用curl发起https请求的时候报错:“SSL certificate problem, verify that the CA cert is OK. Details: erro ...
- C#的百度地图开发(一)发起HTTP请求
原文:C#的百度地图开发(一)发起HTTP请求 百度地图的开发文档中给出了很多的事例,而当用到具体的语言来开发时,又会有些差异.我是使用C#来开发的.在获取相应的数据时,需要通过URL传值,然后获取相 ...
- python 发起HTTP请求
因为微信公众号群发需要调用高级群发接口,其中涉及到python发起HTTP请求,现在将相关实现操作记录如下: 首先,HTTP请求分为GET和POST,如下所示: 首先是发起get 请求: # -*- ...
随机推荐
- P5318 查阅文献
题意大概意思就是分别用dfs与bfs遍历一个图,特殊要求是从编号小的点开始遍历. 用邻接表存图,至今我也没想明白怎么才可以从编号小的点开始遍历,明白是排序,但是不知道如何排序,题解中的排序方法是:按照 ...
- Codeforces Round 883 (Div. 3)
Codeforces Round 883 (Div. 3) A. Rudolph and Cut the Rope 题意:有一颗糖果在连在绳子上,求剪短多少根绳子,他能落地 思路:只要绳子长度比钉子高 ...
- Macbook磁盘系统结构/文件/目录介绍分析
1. 系统磁盘根目录详解: 1.1 磁盘根目录结构 / (根目录)|-- Applications # 存放应用程序|-- Users # 存放用户文件和设置|-- cores # 存放核心转储文件, ...
- 时间加权平均价格算法(TWAP)和成交量平均算法(VWAP)在量化回测的应用
为什么要引入TWAP和 VWAP? 为了评估策略的资金容量,我们对M.trade模块里买入点和卖出点这两个参数进行了更丰富的扩展,支持了策略能够按更丰富的算法交易价格(WAP)进行撮合. 如果资金是1 ...
- python 图片相关
python 图片相关 本篇介绍两种方式来打开图片. 1: 使用matplotlib #!/usr/bin/python3 # -*- coding: UTF-8 -*- ""&q ...
- 【C#】【串口通信(Serial Port)】无法使用(using System.IO.Ports;)命名空间<Error:SerialPort不存在上下文>
1.包缺失导致--安装相应包: 2.等待命令行初始化--输入命令: Install-Package Microsoft.Windows.Compatibility -Version 5.0.2 参考网 ...
- Javascript面向对象的程序设计 —— 自定义类实现继承的6种方法
许多OO语言都支持两种继承方式: 接口继承:只继承方法签名: 实现继承:继承实际的方法.ECMAScript只支持实现继承,实现继承是继承实际的方法,依靠原型链来实现.1.原型链原型链是实现继承的主要 ...
- 2023.2 IDEA安装激活教程
1.下载安装IntelliJ IDEA 先去官网下载,我这里下载的是最新版本的2023.2,测试过2023最新版本以及2022版本以上的版本没问题. 安装然后打开 提示要输入激活码,先关闭应用,等下再 ...
- python脚本抢大麦网演唱会门票 ---保姆级教程 python脚本抢大麦网演唱会门票
python脚本抢大麦网演唱会门票 流程: 1.下载并安装anaconda:https://repo.continuum.io/archive/ 下载对应linux/mac/windows版本 2.下 ...
- DVWA CSRF:Cross-site request forgery(跨站请求伪造)全等级
CSRF:Cross-site request forgery(跨站请求伪造) 目录: CSRF:Cross-site request forgery(跨站请求伪造) 1.Low 2.Medium 3 ...