mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-22 17:31:03 +01:00
216 lines
4.4 KiB
Go
216 lines
4.4 KiB
Go
package req
|
|
|
|
import (
|
|
"encoding/json"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
// Resp represents a request with it's response
|
|
type Resp struct {
|
|
r *Req
|
|
req *http.Request
|
|
resp *http.Response
|
|
client *http.Client
|
|
cost time.Duration
|
|
*multipartHelper
|
|
reqBody []byte
|
|
respBody []byte
|
|
downloadProgress DownloadProgress
|
|
err error // delayed error
|
|
}
|
|
|
|
// Request returns *http.Request
|
|
func (r *Resp) Request() *http.Request {
|
|
return r.req
|
|
}
|
|
|
|
// Response returns *http.Response
|
|
func (r *Resp) Response() *http.Response {
|
|
return r.resp
|
|
}
|
|
|
|
// Bytes returns response body as []byte
|
|
func (r *Resp) Bytes() []byte {
|
|
data, _ := r.ToBytes()
|
|
return data
|
|
}
|
|
|
|
// ToBytes returns response body as []byte,
|
|
// return error if error happend when reading
|
|
// the response body
|
|
func (r *Resp) ToBytes() ([]byte, error) {
|
|
if r.err != nil {
|
|
return nil, r.err
|
|
}
|
|
if r.respBody != nil {
|
|
return r.respBody, nil
|
|
}
|
|
defer r.resp.Body.Close()
|
|
respBody, err := ioutil.ReadAll(r.resp.Body)
|
|
if err != nil {
|
|
r.err = err
|
|
return nil, err
|
|
}
|
|
r.respBody = respBody
|
|
return r.respBody, nil
|
|
}
|
|
|
|
// String returns response body as string
|
|
func (r *Resp) String() string {
|
|
data, _ := r.ToBytes()
|
|
return string(data)
|
|
}
|
|
|
|
// ToString returns response body as string,
|
|
// return error if error happend when reading
|
|
// the response body
|
|
func (r *Resp) ToString() (string, error) {
|
|
data, err := r.ToBytes()
|
|
return string(data), err
|
|
}
|
|
|
|
// ToJSON convert json response body to struct or map
|
|
func (r *Resp) ToJSON(v interface{}) error {
|
|
data, err := r.ToBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, v)
|
|
}
|
|
|
|
// ToXML convert xml response body to struct or map
|
|
func (r *Resp) ToXML(v interface{}) error {
|
|
data, err := r.ToBytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return xml.Unmarshal(data, v)
|
|
}
|
|
|
|
// ToFile download the response body to file with optional download callback
|
|
func (r *Resp) ToFile(name string) error {
|
|
//TODO set name to the suffix of url path if name == ""
|
|
file, err := os.Create(name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
if r.respBody != nil {
|
|
_, err = file.Write(r.respBody)
|
|
return err
|
|
}
|
|
|
|
if r.downloadProgress != nil && r.resp.ContentLength > 0 {
|
|
return r.download(file)
|
|
}
|
|
|
|
defer r.resp.Body.Close()
|
|
_, err = io.Copy(file, r.resp.Body)
|
|
return err
|
|
}
|
|
|
|
func (r *Resp) download(file *os.File) error {
|
|
p := make([]byte, 1024)
|
|
b := r.resp.Body
|
|
defer b.Close()
|
|
total := r.resp.ContentLength
|
|
var current int64
|
|
var lastTime time.Time
|
|
for {
|
|
l, err := b.Read(p)
|
|
if l > 0 {
|
|
_, _err := file.Write(p[:l])
|
|
if _err != nil {
|
|
return _err
|
|
}
|
|
current += int64(l)
|
|
if now := time.Now(); now.Sub(lastTime) > 200*time.Millisecond {
|
|
lastTime = now
|
|
r.downloadProgress(current, total)
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
var regNewline = regexp.MustCompile(`\n|\r`)
|
|
|
|
func (r *Resp) autoFormat(s fmt.State) {
|
|
req := r.req
|
|
if r.r.flag&Lcost != 0 {
|
|
fmt.Fprint(s, req.Method, " ", req.URL.String(), " ", r.cost)
|
|
} else {
|
|
fmt.Fprint(s, req.Method, " ", req.URL.String())
|
|
}
|
|
|
|
// test if it is should be outputed pretty
|
|
var pretty bool
|
|
var parts []string
|
|
addPart := func(part string) {
|
|
if part == "" {
|
|
return
|
|
}
|
|
parts = append(parts, part)
|
|
if !pretty && regNewline.MatchString(part) {
|
|
pretty = true
|
|
}
|
|
}
|
|
if r.r.flag&LreqBody != 0 { // request body
|
|
addPart(string(r.reqBody))
|
|
}
|
|
if r.r.flag&LrespBody != 0 { // response body
|
|
addPart(r.String())
|
|
}
|
|
|
|
for _, part := range parts {
|
|
if pretty {
|
|
fmt.Fprint(s, "\n")
|
|
}
|
|
fmt.Fprint(s, " ", part)
|
|
}
|
|
}
|
|
|
|
func (r *Resp) miniFormat(s fmt.State) {
|
|
req := r.req
|
|
if r.r.flag&Lcost != 0 {
|
|
fmt.Fprint(s, req.Method, " ", req.URL.String(), " ", r.cost)
|
|
} else {
|
|
fmt.Fprint(s, req.Method, " ", req.URL.String())
|
|
}
|
|
if r.r.flag&LreqBody != 0 && len(r.reqBody) > 0 { // request body
|
|
str := regNewline.ReplaceAllString(string(r.reqBody), " ")
|
|
fmt.Fprint(s, " ", str)
|
|
}
|
|
if r.r.flag&LrespBody != 0 && r.String() != "" { // response body
|
|
str := regNewline.ReplaceAllString(r.String(), " ")
|
|
fmt.Fprint(s, " ", str)
|
|
}
|
|
}
|
|
|
|
// Format fort the response
|
|
func (r *Resp) Format(s fmt.State, verb rune) {
|
|
if r == nil || r.req == nil {
|
|
return
|
|
}
|
|
if s.Flag('+') { // include header and format pretty.
|
|
fmt.Fprint(s, r.Dump())
|
|
} else if s.Flag('-') { // keep all informations in one line.
|
|
r.miniFormat(s)
|
|
} else { // auto
|
|
r.autoFormat(s)
|
|
}
|
|
}
|