├── .gitignore ├── .whitesource ├── README.md ├── _example ├── example.go ├── get.go ├── post.go └── upload.go ├── fileupload.go ├── firehttp.go ├── request.go └── response.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /.whitesource: -------------------------------------------------------------------------------- 1 | ########################################################## 2 | #### WhiteSource "Bolt for Github" configuration file #### 3 | ########################################################## 4 | 5 | # Configuration # 6 | #---------------# 7 | ws.repo.scan=true 8 | vulnerable.check.run.conclusion.level=success -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # firehttp 2 | 3 | 4 | 一个专门用于开发安全工具的HTTP类库. 5 | 6 | 7 | ### 特点 8 | 9 | 1. header、params、body 均支持string和map定义, 太方便了. 10 | 2. 可以获取到HTTP请求/响应的原始HTTP报文. 11 | 3. 使用起来,真心简单. 12 | 4. 尽量用优雅的编程方式,让go代码读起来不那么丑陋. 13 | 5. 自己摸索吧. 14 | 15 | ### Example 16 | 17 | 发送一个普通的GET请求 18 | 19 | ```go 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "github.com/dean2021/firehttp" 26 | "log" 27 | ) 28 | 29 | func main() { 30 | 31 | f := firehttp.New(nil) 32 | resp, err := f.Get("http://www.jd.com", nil) 33 | if err != nil { 34 | log.Fatal(err) 35 | } 36 | fmt.Println(resp.String()) 37 | } 38 | 39 | 40 | ``` 41 | 42 | 发送一个复杂的GET请求 43 | 44 | ```go 45 | package main 46 | 47 | import ( 48 | "github.com/dean2021/firehttp" 49 | "time" 50 | "log" 51 | "fmt" 52 | ) 53 | 54 | func main() { 55 | 56 | f := firehttp.New(&firehttp.HTTPOptions{ 57 | 58 | // HTTP代理地址,推荐用burpsuite进行调试 59 | Proxy: "http://127.0.0.1:8080", 60 | 61 | // DNS缓存有效时间 62 | DNSCacheExpire: time.Minute * 5, 63 | 64 | // 空闲链接 65 | MaxIdleConn: 1, 66 | 67 | // HTTP握手超时设置 68 | TLSHandshakeTimeout: time.Second * 5, 69 | 70 | // 拨号建立连接完成超时时间 71 | DialTimeout: time.Second * 5, 72 | 73 | // KeepAlive 超时时间 74 | DialKeepAlive: time.Second * 5, 75 | }) 76 | 77 | resp, err := f.Get("https://www.jd.com/index.php", &firehttp.ReqOptions{ 78 | 79 | // GET参数,支持map[string]string 和 string 80 | Params: "id=1", 81 | 82 | // header参数,支持map[string]string 和 string 83 | Header: "Cookie: xxxx\r\n", 84 | 85 | // 请求总超时时间 86 | Timeout: time.Second * 10, 87 | 88 | // 表示这个请求是一个ajax请求,自动追加Content-Type 89 | IsAjax: true, 90 | 91 | // 禁止跳转 92 | DisableRedirect: true, 93 | 94 | // 使用cookie会话 95 | UseCookieJar:true, 96 | 97 | // 跳过https证书验证 98 | InsecureSkipVerify:true, 99 | 100 | // 禁止gzip请求压缩 101 | DisableCompression: true, 102 | }) 103 | 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | 108 | 109 | // 输出HTTP请求报文 110 | fmt.Println(resp.RawHTTPRequest()) 111 | 112 | // 输出HTTP响应报文 113 | fmt.Println(resp.RawHTTPResponse()) 114 | 115 | // 输出响应状态码 116 | fmt.Println(resp.StatusCode()) 117 | 118 | // 输出响应内容 119 | fmt.Println(resp.String()) 120 | } 121 | 122 | 123 | ``` 124 | 125 | 发送一个POST请求 126 | 127 | ```go 128 | package main 129 | 130 | import ( 131 | "fmt" 132 | "github.com/dean2021/firehttp" 133 | "log" 134 | ) 135 | 136 | func main() { 137 | 138 | f := firehttp.New(nil) 139 | resp, err := f.Post("https://www.jd.com", &firehttp.ReqOptions{ 140 | Body: "xxxx", 141 | }) 142 | if err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | fmt.Println(resp.RawHTTPRequest()) 147 | } 148 | ``` 149 | 上传一个文件 150 | 151 | ```go 152 | package main 153 | 154 | import ( 155 | "fmt" 156 | "github.com/dean2021/firehttp" 157 | "log" 158 | ) 159 | 160 | func main() { 161 | 162 | f := firehttp.New(nil) 163 | resp, err := f.Post("http://www.baidu.com/upload.php", &firehttp.ReqOptions{ 164 | Files: []firehttp.FileUpload{ 165 | { 166 | FieldName: "passwd", 167 | FileName: "/etc/passwd", 168 | }, 169 | }, 170 | }) 171 | if err != nil { 172 | log.Fatal(err) 173 | } 174 | fmt.Println(resp.StatusCode()) 175 | } 176 | ``` 177 | 178 | 179 | 代码简单易懂,建议有空通读一遍代码,相信会有新的发现。 180 | 181 | 182 | 183 | ## 使用文档 184 | 185 | 186 | ### HTTP OPTION 设置参数 187 | ```go 188 | 189 | // HTTP Request 参数 190 | type HTTPOptions struct { 191 | // 代理地址 192 | Proxy string 193 | 194 | // DNS缓存有效时间 195 | DNSCacheExpire time.Duration 196 | 197 | // 空闲连接数 198 | MaxIdleConn int 199 | 200 | // TLS握手超时时间 201 | TLSHandshakeTimeout time.Duration 202 | 203 | // 拨号建立连接完成超时时间 204 | DialTimeout time.Duration 205 | 206 | // KeepAlive 超时时间 207 | DialKeepAlive time.Duration 208 | } 209 | ``` 210 | 211 | ### Request Option 设置参数 212 | 213 | ```go 214 | 215 | // HTTP Request 参数 216 | type ReqOptions struct { 217 | // 请求get参数 218 | Params interface{} 219 | 220 | // 设置请求header 221 | Header interface{} 222 | 223 | // 整个请求(包括拨号/请求/重定向)等待的最长时间。 224 | Timeout time.Duration 225 | 226 | // 上传文件 227 | Files []FileUpload 228 | 229 | // 禁用跳转 230 | DisableRedirect bool 231 | 232 | // 请求body 233 | Body interface{} 234 | 235 | // 基础认证账号密码 236 | BasicAuthUserAndPass []string 237 | 238 | // 设置ajax header 239 | IsAjax bool 240 | 241 | // 设置JSON header 242 | IsJSON bool 243 | 244 | // 设置XML header 245 | IsXML bool 246 | 247 | // 自定义http client 248 | HTTPClient *http.Client 249 | 250 | // 使用cookie会话 251 | UseCookieJar bool 252 | CookieJar http.CookieJar 253 | 254 | // 跳过证书验证 255 | InsecureSkipVerify bool 256 | 257 | // 禁用请求gzip压缩 258 | DisableCompression bool 259 | } 260 | 261 | ``` 262 | 文档待完善... 263 | 264 | ## thanks 265 | 266 | 大量参考这个两个类库的代码, 特此感谢 levigross 和 Gay4 同学. 267 | 268 | 1. grequests / https://github.com/levigross/grequests 269 | 2. zhttp / https://github.com/Greyh4t/zhttp -------------------------------------------------------------------------------- /_example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/dean2021/firehttp" 5 | "time" 6 | "log" 7 | "fmt" 8 | ) 9 | 10 | func main() { 11 | 12 | f := firehttp.New(&firehttp.HTTPOptions{ 13 | 14 | // HTTP代理地址,推荐用burpsuite进行调试 15 | Proxy: "http://127.0.0.1:8080", 16 | 17 | // DNS缓存有效时间 18 | DNSCacheExpire: time.Minute * 5, 19 | 20 | // 空闲链接 21 | MaxIdleConn: 1, 22 | 23 | // HTTP握手超时设置 24 | TLSHandshakeTimeout: time.Second * 5, 25 | 26 | // 拨号建立连接完成超时时间 27 | DialTimeout: time.Second * 5, 28 | 29 | // KeepAlive 超时时间 30 | DialKeepAlive: time.Second * 5, 31 | 32 | // 预设好的header 33 | ParentHeader: map[string]string{ 34 | "User-Agent": "test", 35 | }, 36 | 37 | ParentHTTPTimeout: time.Second * 1, 38 | }) 39 | 40 | resp, err := f.Get("https://www.jd.com/index.php", &firehttp.ReqOptions{ 41 | 42 | // GET参数,支持map[string]string 和 string 43 | Params: "id=1", 44 | 45 | // header参数,支持map[string]string 和 string 46 | Header: "Cookie: xxxx\r\n", 47 | 48 | // 请求总超时时间 49 | Timeout: time.Second * 10, 50 | 51 | // 表示这个请求是一个ajax请求,自动追加Content-Type 52 | IsAjax: true, 53 | 54 | // 禁止跳转 55 | DisableRedirect: true, 56 | 57 | // 使用cookie会话 58 | UseCookieJar:true, 59 | 60 | // 跳过https证书验证 61 | InsecureSkipVerify:true, 62 | 63 | // 禁止gzip请求压缩 64 | DisableCompression: true, 65 | }) 66 | 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | 72 | // 输出HTTP请求报文 73 | fmt.Println(resp.RawHTTPRequest()) 74 | 75 | // 输出HTTP响应报文 76 | fmt.Println(resp.RawHTTPResponse()) 77 | 78 | // 输出响应状态码 79 | fmt.Println(resp.StatusCode()) 80 | 81 | // 输出响应内容 82 | fmt.Println(resp.String()) 83 | } 84 | -------------------------------------------------------------------------------- /_example/get.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dean2021/firehttp" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | 11 | f := firehttp.New(nil) 12 | resp, err := f.Get("http://www.jd.com", nil) 13 | if err != nil { 14 | log.Fatal(err) 15 | } 16 | fmt.Println(resp.String()) 17 | } 18 | -------------------------------------------------------------------------------- /_example/post.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dean2021/firehttp" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | 11 | f := firehttp.New(nil) 12 | resp, err := f.Post("https://www.jd.com", &firehttp.ReqOptions{ 13 | Body: "xxxx", 14 | }) 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | fmt.Println(resp.RawHTTPRequest()) 20 | } 21 | -------------------------------------------------------------------------------- /_example/upload.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dean2021/firehttp" 6 | "log" 7 | ) 8 | 9 | func main() { 10 | 11 | f := firehttp.New(nil) 12 | resp, err := f.Post("http://www.baidu.com/upload.php", &firehttp.ReqOptions{ 13 | Body: map[string]string{ 14 | "foo": "bar", 15 | }, 16 | Files: []firehttp.FileUpload{ 17 | { 18 | FieldName: "passwd", 19 | FileName: "/etc/passwd", 20 | }, 21 | }, 22 | }) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | fmt.Println(resp.StatusCode()) 27 | } 28 | -------------------------------------------------------------------------------- /fileupload.go: -------------------------------------------------------------------------------- 1 | package firehttp 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | ) 7 | 8 | type FileUpload struct { 9 | FileName string 10 | 11 | FileBody io.ReadCloser 12 | 13 | FieldName string 14 | 15 | // 默认 application/octet-stream 16 | FileMime string 17 | } 18 | 19 | var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 20 | 21 | func escapeQuotes(s string) string { 22 | return quoteEscaper.Replace(s) 23 | } 24 | -------------------------------------------------------------------------------- /firehttp.go: -------------------------------------------------------------------------------- 1 | package firehttp 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "fmt" 7 | "github.com/Greyh4t/dnscache" 8 | "github.com/google/go-querystring/query" 9 | "github.com/pkg/errors" 10 | "golang.org/x/net/publicsuffix" 11 | "io" 12 | "mime/multipart" 13 | "net" 14 | "net/http" 15 | "net/http/cookiejar" 16 | "net/textproto" 17 | "net/url" 18 | "os" 19 | "runtime" 20 | "strconv" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | const VERSION = "0.1" 26 | 27 | type FireHttp struct { 28 | Setting *HTTPOptions 29 | } 30 | 31 | // HTTP Request 参数 32 | type HTTPOptions struct { 33 | // 代理地址 34 | Proxy string 35 | 36 | // DNS缓存有效时间 37 | DNSCacheExpire time.Duration 38 | 39 | // 空闲连接数 40 | MaxIdleConn int 41 | 42 | // TLS握手超时时间 43 | TLSHandshakeTimeout time.Duration 44 | 45 | // 拨号建立连接完成超时时间 46 | DialTimeout time.Duration 47 | 48 | // KeepAlive 超时时间 49 | DialKeepAlive time.Duration 50 | 51 | // 预先设置的Header 52 | ParentHeader map[string]string 53 | 54 | // 预先设置好的http请求超时时间 55 | ParentHTTPTimeout time.Duration 56 | } 57 | 58 | var resolver *dnscache.Resolver 59 | 60 | // 发送请求 61 | func (f *FireHttp) DoRequest(method string, rawUrl string, ro *ReqOptions) (*Response, error) { 62 | 63 | if ro == nil { 64 | ro = &ReqOptions{} 65 | } 66 | 67 | // 默认请求超时时间 68 | if ro.Timeout == 0 { 69 | if f.Setting.ParentHTTPTimeout != 0 { 70 | ro.Timeout = f.Setting.ParentHTTPTimeout 71 | } else { 72 | ro.Timeout = 60 * time.Second 73 | } 74 | } 75 | 76 | var httpClient *http.Client 77 | rawURL, err := buildQuery(rawUrl, ro.Params) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | req, err := f.BuildRequest(method, rawURL, ro) 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | if ro.HTTPClient != nil { 88 | httpClient = ro.HTTPClient 89 | } else { 90 | httpClient = f.buildHTTPClient(ro) 91 | } 92 | 93 | resp, err := httpClient.Do(req) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return &Response{RawResponse: resp}, nil 99 | } 100 | 101 | // 构建HTTP Client 102 | func (f *FireHttp) buildHTTPClient(ro *ReqOptions) *http.Client { 103 | 104 | var cookieJar http.CookieJar 105 | if ro.UseCookieJar { 106 | if ro.CookieJar != nil { 107 | cookieJar = ro.CookieJar 108 | } else { 109 | cookieJar, _ = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) 110 | } 111 | } 112 | 113 | return &http.Client{ 114 | CheckRedirect: func(req *http.Request, via []*http.Request) error { 115 | if ro.DisableRedirect { 116 | return http.ErrUseLastResponse 117 | } else { 118 | return nil 119 | } 120 | }, 121 | Jar: cookieJar, 122 | Transport: f.buildHTTPTransport(ro), 123 | Timeout: ro.Timeout, 124 | } 125 | } 126 | 127 | // 构建Transport 128 | func (f *FireHttp) buildHTTPTransport(ro *ReqOptions) *http.Transport { 129 | transport := &http.Transport{ 130 | Proxy: func(request *http.Request) (*url.URL, error) { 131 | if f.Setting.Proxy != "" { 132 | return url.Parse(f.Setting.Proxy) 133 | } 134 | return nil, nil 135 | }, 136 | TLSClientConfig: &tls.Config{InsecureSkipVerify: ro.InsecureSkipVerify}, 137 | DisableCompression: ro.DisableCompression, 138 | MaxIdleConns: f.Setting.MaxIdleConn, 139 | IdleConnTimeout: f.Setting.DialKeepAlive, 140 | TLSHandshakeTimeout: f.Setting.DialKeepAlive, 141 | ExpectContinueTimeout: f.Setting.DialKeepAlive, 142 | Dial: func(network, addr string) (net.Conn, error) { 143 | deadline := time.Now().Add(f.Setting.DialKeepAlive) 144 | if resolver != nil { 145 | host, port, err := net.SplitHostPort(addr) 146 | if err == nil { 147 | ip, err := resolver.FetchOneString(host) 148 | if err != nil { 149 | return nil, err 150 | } 151 | addr = net.JoinHostPort(ip, port) 152 | } 153 | } 154 | c, err := net.DialTimeout(network, addr, f.Setting.DialKeepAlive) 155 | if err != nil { 156 | return nil, err 157 | } 158 | c.SetDeadline(deadline) 159 | c.SetReadDeadline(deadline) 160 | c.SetWriteDeadline(deadline) 161 | return c, nil 162 | }, 163 | } 164 | 165 | EnsureTransporterFinalized(transport) 166 | 167 | return transport 168 | } 169 | 170 | // 摘抄grequests,解决资源泄露问题 171 | // EnsureTransporterFinalized will ensure that when the HTTP client is GCed 172 | // the runtime will close the idle connections (so that they won't leak) 173 | // this function was adopted from Hashicorp's go-cleanhttp package 174 | func EnsureTransporterFinalized(httpTransport *http.Transport) { 175 | runtime.SetFinalizer(&httpTransport, func(transportInt **http.Transport) { 176 | (*transportInt).CloseIdleConnections() 177 | }) 178 | } 179 | 180 | // 构建请求 181 | // 支持body格式string或[]byte 182 | func (f *FireHttp) BuildRequest(method string, rawURL string, ro *ReqOptions) (*http.Request, error) { 183 | 184 | req, err := http.NewRequest(method, rawURL, nil) 185 | if err != nil { 186 | return nil, err 187 | } 188 | 189 | if ro.Body != nil { 190 | req, err = f.BuildBasicRequest(method, rawURL, ro) 191 | if err != nil { 192 | return nil, err 193 | } 194 | } 195 | 196 | if ro.Files != nil { 197 | req, err = BuildFileUploadRequest(method, rawURL, ro) 198 | if err != nil { 199 | return nil, err 200 | } 201 | } 202 | 203 | err = f.SetHeaders(req, ro) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | return req, nil 209 | } 210 | 211 | // 构建基础请求 212 | func (f *FireHttp) BuildBasicRequest(method string, rawURL string, ro *ReqOptions) (*http.Request, error) { 213 | var reader io.Reader 214 | switch ro.Body.(type) { 215 | case string: 216 | reader = strings.NewReader(ro.Body.(string)) 217 | case []byte: 218 | reader = bytes.NewReader(ro.Body.([]byte)) 219 | case map[string]string: 220 | urlValues := &url.Values{} 221 | for key, value := range ro.Body.(map[string]string) { 222 | urlValues.Set(key, value) 223 | } 224 | reader = strings.NewReader(urlValues.Encode()) 225 | default: 226 | return nil, errors.New("body type error, only support string and map[string]string or []byte type") 227 | } 228 | req, err := http.NewRequest(method, rawURL, reader) 229 | if err != nil { 230 | return nil, err 231 | } 232 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 233 | return req, nil 234 | } 235 | 236 | // 构建文件上传请求 237 | func BuildFileUploadRequest(method string, rawURL string, ro *ReqOptions) (*http.Request, error) { 238 | 239 | if method == "POST" { 240 | return buildPostFileUploadRequest(method, rawURL, ro) 241 | } 242 | 243 | // 目前仅支持POST上传 244 | return nil, errors.New("Upload method is wrong, currently only supports POST method to upload files") 245 | } 246 | 247 | // 构建post上传文件 248 | // 支持上传多个文件 249 | func buildPostFileUploadRequest(method string, rawURL string, ro *ReqOptions) (*http.Request, error) { 250 | 251 | requestBody := &bytes.Buffer{} 252 | multipartWriter := multipart.NewWriter(requestBody) 253 | 254 | for i, f := range ro.Files { 255 | fd, err := os.Open(f.FileName) 256 | if err != nil { 257 | return nil, err 258 | } 259 | ro.Files[i].FileBody = fd 260 | } 261 | 262 | for i, f := range ro.Files { 263 | 264 | fieldName := f.FieldName 265 | 266 | if fieldName == "" { 267 | if len(ro.Files) > 1 { 268 | fieldName = strings.Join([]string{"file", strconv.Itoa(i + 1)}, "") 269 | } else { 270 | fieldName = "file" 271 | } 272 | } 273 | 274 | var writer io.Writer 275 | var err error 276 | 277 | if f.FileMime != "" { 278 | if f.FileName == "" { 279 | f.FileName = "filename" 280 | } 281 | h := make(textproto.MIMEHeader) 282 | h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(fieldName), escapeQuotes(f.FileName))) 283 | h.Set("Content-Type", f.FileMime) 284 | writer, err = multipartWriter.CreatePart(h) 285 | } else { 286 | writer, err = multipartWriter.CreateFormFile(fieldName, f.FileName) 287 | } 288 | 289 | if err != nil { 290 | return nil, err 291 | } 292 | 293 | if _, err = io.Copy(writer, f.FileBody); err != nil && err != io.EOF { 294 | return nil, err 295 | } 296 | 297 | if err := f.FileBody.Close(); err != nil { 298 | return nil, err 299 | } 300 | } 301 | 302 | if ro.Body != nil { 303 | switch ro.Body.(type) { 304 | case string: 305 | requestBody.Write([]byte(ro.Body.(string))) 306 | case []byte: 307 | requestBody.Write(ro.Body.([]byte)) 308 | case map[string]string: 309 | for key, value := range ro.Body.(map[string]string) { 310 | _ = multipartWriter.WriteField(key, value) 311 | } 312 | default: 313 | return nil, errors.New("body type error, only support string and map[string]string or []byte type") 314 | } 315 | } 316 | if err := multipartWriter.Close(); err != nil { 317 | return nil, err 318 | } 319 | 320 | req, err := http.NewRequest(method, rawURL, requestBody) 321 | 322 | if err != nil { 323 | return nil, err 324 | } 325 | 326 | req.Header.Add("Content-Type", multipartWriter.FormDataContentType()) 327 | 328 | return req, err 329 | 330 | } 331 | 332 | // 设置header 333 | // 仅支持string和map[string]string类型 334 | func (f *FireHttp) SetHeaders(req *http.Request, ro *ReqOptions) error { 335 | 336 | var err error 337 | 338 | if f.Setting.ParentHeader != nil { 339 | for key, val := range f.Setting.ParentHeader { 340 | req.Header.Set(key, val) 341 | } 342 | } 343 | 344 | if ro.Header != nil { 345 | switch ro.Header.(type) { 346 | case map[string]string: 347 | for key, val := range ro.Header.(map[string]string) { 348 | req.Header.Set(key, val) 349 | } 350 | case string: 351 | headers := strings.Split(ro.Header.(string), "\r\n") 352 | for _, val := range headers { 353 | header := strings.Split(val, ":") 354 | 355 | if len(header) == 2 { 356 | req.Header.Set(header[0], header[1]) 357 | } 358 | } 359 | default: 360 | return errors.New("header type error, only support string and map[string]string type") 361 | } 362 | } 363 | 364 | // 基础认证 365 | if ro.BasicAuthUserAndPass != nil { 366 | if len(ro.BasicAuthUserAndPass) == 2 { 367 | req.SetBasicAuth(ro.BasicAuthUserAndPass[0], ro.BasicAuthUserAndPass[1]) 368 | } 369 | } 370 | 371 | // 启用ajax请求 372 | if ro.IsAjax == true { 373 | req.Header.Set("X-Requested-With", "XMLHttpRequest") 374 | } 375 | 376 | if ro.IsJSON == true { 377 | req.Header.Set("Content-Type", "application/json") 378 | } 379 | 380 | if ro.IsXML == true { 381 | req.Header.Set("Content-Type", "application/xml") 382 | } 383 | 384 | if req.Header.Get("User-Agent") == "" { 385 | req.Header.Set("User-Agent", "firehttp/"+VERSION) 386 | } 387 | 388 | return err 389 | } 390 | 391 | // 解析params,生成带Query的url 392 | // params 支持string类型和map[string]string或struct类型 393 | func buildQuery(rawURL string, params interface{}) (string, error) { 394 | 395 | if params != nil { 396 | 397 | parsedURL, err := url.Parse(rawURL) 398 | if err != nil { 399 | return "", err 400 | } 401 | 402 | parsedQuery, err := url.ParseQuery(parsedURL.RawQuery) 403 | if err != nil { 404 | return "", err 405 | } 406 | 407 | switch params.(type) { 408 | case string: 409 | urlQuery := params.(string) 410 | queryStr, err := url.ParseQuery(urlQuery) 411 | if err != nil { 412 | return "", err 413 | } 414 | for key, value := range queryStr { 415 | for _, v := range value { 416 | parsedQuery.Add(key, v) 417 | } 418 | } 419 | case map[string]string: 420 | for key, value := range params.(map[string]string) { 421 | parsedQuery.Set(key, value) 422 | } 423 | case struct{}: 424 | queryStruct, err := query.Values(params) 425 | if err != nil { 426 | return "", err 427 | } 428 | for key, value := range queryStruct { 429 | for _, v := range value { 430 | parsedQuery.Add(key, v) 431 | } 432 | } 433 | default: 434 | return "", errors.New("params type error, only support string and map[string]string or struct type") 435 | } 436 | rawURL = strings.Join([]string{strings.Replace(parsedURL.String(), "?"+parsedURL.RawQuery, "", -1), parsedQuery.Encode()}, "?") 437 | } 438 | return rawURL, nil 439 | } 440 | 441 | // HTTP Request 参数 442 | type ReqOptions struct { 443 | // 请求get参数 444 | Params interface{} 445 | 446 | // 设置请求header 447 | Header interface{} 448 | 449 | // 整个请求(包括拨号/请求/重定向)等待的最长时间。 450 | Timeout time.Duration 451 | 452 | // 上传文件 453 | Files []FileUpload 454 | 455 | // 禁用跳转 456 | DisableRedirect bool 457 | 458 | // 请求body 459 | Body interface{} 460 | 461 | // 基础认证账号密码 462 | BasicAuthUserAndPass []string 463 | 464 | // 设置ajax header 465 | IsAjax bool 466 | 467 | // 设置JSON header 468 | IsJSON bool 469 | 470 | // 设置XML header 471 | IsXML bool 472 | 473 | // 自定义http client 474 | HTTPClient *http.Client 475 | 476 | // 使用cookie会话 477 | UseCookieJar bool 478 | CookieJar http.CookieJar 479 | 480 | // 跳过证书验证 481 | InsecureSkipVerify bool 482 | 483 | // 禁用请求gzip压缩 484 | DisableCompression bool 485 | } 486 | 487 | func New(options *HTTPOptions) *FireHttp { 488 | 489 | if options == nil { 490 | options = &HTTPOptions{} 491 | } 492 | 493 | // 设置默认超时 494 | if options.TLSHandshakeTimeout == 0 { 495 | options.TLSHandshakeTimeout = 10 * time.Second 496 | } 497 | 498 | if options.DialTimeout == 0 { 499 | options.DialTimeout = 30 * time.Second 500 | } 501 | 502 | if options.DialKeepAlive == 0 { 503 | options.DialKeepAlive = 30 * time.Second 504 | } 505 | 506 | if options.DNSCacheExpire > 0 { 507 | resolver = dnscache.New(options.DNSCacheExpire) 508 | } 509 | 510 | return &FireHttp{ 511 | Setting: options, 512 | } 513 | 514 | } 515 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package firehttp 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | ) 8 | 9 | // 发送GET请求 10 | func (f *FireHttp) Get(rawUrl string, options *ReqOptions) (*Response, error) { 11 | return f.DoRequest("GET", rawUrl, options) 12 | } 13 | 14 | // 发送POST请求 15 | func (f *FireHttp) Post(rawUrl string, options *ReqOptions) (*Response, error) { 16 | return f.DoRequest("POST", rawUrl, options) 17 | } 18 | 19 | // 发送PUT请求 20 | func (f *FireHttp) Put(rawUrl string, options *ReqOptions) (*Response, error) { 21 | return f.DoRequest("PUT", rawUrl, options) 22 | } 23 | 24 | // 发送Del请求 25 | func (f *FireHttp) Del(rawUrl string, options *ReqOptions) (*Response, error) { 26 | return f.DoRequest("DELETE", rawUrl, options) 27 | } 28 | 29 | 30 | // 发送Haed请求 31 | func (f *FireHttp) Head(rawUrl string, options *ReqOptions) (*Response, error) { 32 | return f.DoRequest("HEAD", rawUrl, options) 33 | } 34 | 35 | // 发送Patch请求 36 | func (f *FireHttp) Patch(rawUrl string, options *ReqOptions) (*Response, error) { 37 | return f.DoRequest("PATCH", rawUrl, options) 38 | } 39 | 40 | // 发送Options请求 41 | func (f *FireHttp) Options(rawUrl string, options *ReqOptions) (*Response, error) { 42 | return f.DoRequest("OPTIONS", rawUrl, options) 43 | } 44 | 45 | // 获取原始的http请求报文 46 | func RawHTTPRequest(req *http.Request) string { 47 | rawRequest := req.Method + " " + req.URL.RequestURI() + " " + req.Proto + "\r\n" 48 | host := req.Host 49 | if host == "" { 50 | host = req.URL.Host 51 | } 52 | rawRequest += "Host: " + host + "\r\n" 53 | for key, val := range req.Header { 54 | rawRequest += key + ": " + val[0] + "\r\n" 55 | } 56 | rawRequest += "\r\n" 57 | if req.GetBody != nil { 58 | b, err := req.GetBody() 59 | if err == nil { 60 | buf, _ := ioutil.ReadAll(b) 61 | rawRequest += string(buf) 62 | } 63 | } 64 | return rawRequest 65 | } 66 | 67 | // 获取原始的http请求报文 68 | func RawHTTPResponse(resp *http.Response) string { 69 | httpMsg := fmt.Sprintf("%s %s \r\n", resp.Proto, resp.Status) 70 | for key, val := range resp.Header { 71 | httpMsg += fmt.Sprintf("%s:%s \r\n", key, val[0]) 72 | } 73 | httpMsg += "\r\n" 74 | buf, _ := ioutil.ReadAll(resp.Body) 75 | httpMsg += string(buf) 76 | return httpMsg 77 | } 78 | -------------------------------------------------------------------------------- /response.go: -------------------------------------------------------------------------------- 1 | package firehttp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | ) 10 | 11 | type Response struct { 12 | RawResponse *http.Response 13 | } 14 | 15 | func (r *Response) StatusCode() int { 16 | return r.RawResponse.StatusCode 17 | } 18 | 19 | func (r *Response) Headers() http.Header { 20 | return r.RawResponse.Header 21 | } 22 | 23 | func (r *Response) Cookies() []*http.Cookie { 24 | return r.RawResponse.Cookies() 25 | } 26 | 27 | // 获取原始header 28 | func (r *Response) RawHeaders() string { 29 | var rawHeader string 30 | for k, v := range r.RawResponse.Header { 31 | for _, value := range v { 32 | rawHeader += k + ": " + value + "\r\n" 33 | } 34 | } 35 | return strings.TrimSuffix(rawHeader, "\r\n") 36 | } 37 | 38 | // 获取原始的set cookie 39 | func (r *Response) RawCookies() string { 40 | var rawCookie string 41 | for _, v := range r.RawResponse.Cookies() { 42 | rawCookie += fmt.Sprintf("Set-Cookie: %s\r\n", v.Raw) 43 | } 44 | return rawCookie 45 | } 46 | 47 | // 获取response响应内容,string格式 48 | func (r *Response) String() string { 49 | body, _ := ioutil.ReadAll(r.RawResponse.Body) 50 | return string(body) 51 | } 52 | 53 | // 获取response响应内容,byte格式 54 | func (r *Response) Byte() []byte { 55 | body, _ := ioutil.ReadAll(r.RawResponse.Body) 56 | return body 57 | } 58 | 59 | // 读取指定长度的body 60 | func (r *Response) ReadN(n int64) []byte { 61 | body, _ := ioutil.ReadAll(io.LimitReader(r.RawResponse.Body, n)) 62 | return body 63 | } 64 | 65 | // 获取原始HTTP请求报文 66 | func (r *Response) RawHTTPRequest() string { 67 | return RawHTTPRequest(r.RawResponse.Request) 68 | } 69 | 70 | // 获取原始HTTP响应报文 71 | func (r *Response) RawHTTPResponse() string { 72 | return RawHTTPResponse(r.RawResponse) 73 | } 74 | 75 | func (r *Response) Close() error { 76 | if r.RawResponse.Body != nil { 77 | return r.RawResponse.Body.Close() 78 | } else { 79 | return nil 80 | } 81 | } 82 | --------------------------------------------------------------------------------