diff --git a/examples/demo.har b/examples/demo.har new file mode 100644 index 00000000..eb5e0365 --- /dev/null +++ b/examples/demo.har @@ -0,0 +1,231 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Charles Proxy", + "version": "4.6.1" + }, + "entries": [ + { + "startedDateTime": "2021-10-11T20:33:08.070+08:00", + "time": 1348, + "request": { + "method": "GET", + "url": "https://postman-echo.com/get?foo1=Kie7p&foo2=34.5", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Host", + "value": "postman-echo.com" + }, + { + "name": "User-Agent", + "value": "HttpBoomer" + }, + { + "name": "Accept-Encoding", + "value": "gzip" + } + ], + "queryString": [ + { + "name": "foo1", + "value": "Kie7p" + }, + { + "name": "foo2", + "value": "34.5" + } + ], + "headersSize": 113, + "bodySize": 0 + }, + "response": { + "_charlesStatus": "COMPLETE", + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [ + { + "name": "sails.sid", + "value": "s%3AOf5eKs36eD8xIj5lio09zXrhVra1yPHo.PJyQYxoyPvXNUPal4Tj0OaJDLDJ7tZfZ%2BCL37XxD5Qg", + "path": "/", + "domain": null, + "expires": null, + "httpOnly": true, + "secure": false, + "comment": null, + "_maxAge": null + } + ], + "headers": [ + { + "name": "Date", + "value": "Mon, 11 Oct 2021 12:33:09 GMT" + }, + { + "name": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "name": "Content-Length", + "value": "300" + }, + { + "name": "ETag", + "value": "W/\"12c-R4rXPU4pP2zRVaUP5eVfknZVx78\"" + }, + { + "name": "Vary", + "value": "Accept-Encoding" + }, + { + "name": "set-cookie", + "value": "sails.sid=s%3AOf5eKs36eD8xIj5lio09zXrhVra1yPHo.PJyQYxoyPvXNUPal4Tj0OaJDLDJ7tZfZ%2BCL37XxD5Qg; Path=/; HttpOnly" + }, + { + "name": "Connection", + "value": "keep-alive" + } + ], + "content": { + "size": 300, + "mimeType": "application/json; charset=utf-8", + "text": "eyJhcmdzIjp7ImZvbzEiOiJLaWU3cCIsImZvbzIiOiIzNC41In0sImhlYWRlcnMiOnsieC1mb3J3YXJkZWQtcHJvdG8iOiJodHRwcyIsIngtZm9yd2FyZGVkLXBvcnQiOiI0NDMiLCJob3N0IjoicG9zdG1hbi1lY2hvLmNvbSIsIngtYW16bi10cmFjZS1pZCI6IlJvb3Q9MS02MTY0MmYwNS0xOTZjNjZhNDUzYjEyNTk4NTNiYzc1NGMiLCJ1c2VyLWFnZW50IjoiSHR0cEJvb21lciIsImFjY2VwdC1lbmNvZGluZyI6Imd6aXAifSwidXJsIjoiaHR0cHM6Ly9wb3N0bWFuLWVjaG8uY29tL2dldD9mb28xPUtpZTdwJmZvbzI9MzQuNSJ9", + "encoding": "base64" + }, + "redirectURL": null, + "headersSize": 0, + "bodySize": 300 + }, + "serverIPAddress": "54.209.48.16", + "cache": {}, + "timings": { + "dns": 137, + "connect": 938, + "ssl": 579, + "send": 1, + "wait": 271, + "receive": 1 + } + }, + { + "startedDateTime": "2021-10-11T20:33:09.646+08:00", + "time": 273, + "request": { + "method": "POST", + "url": "https://postman-echo.com/post", + "httpVersion": "HTTP/1.1", + "cookies": [ + { + "name": "sails.sid", + "value": "s%3AOf5eKs36eD8xIj5lio09zXrhVra1yPHo.PJyQYxoyPvXNUPal4Tj0OaJDLDJ7tZfZ%2BCL37XxD5Qg" + } + ], + "headers": [ + { + "name": "Host", + "value": "postman-echo.com" + }, + { + "name": "User-Agent", + "value": "Go-http-client/1.1" + }, + { + "name": "Content-Length", + "value": "28" + }, + { + "name": "Content-Type", + "value": "application/json; charset=UTF-8" + }, + { + "name": "Cookie", + "value": "sails.sid=s%3AOf5eKs36eD8xIj5lio09zXrhVra1yPHo.PJyQYxoyPvXNUPal4Tj0OaJDLDJ7tZfZ%2BCL37XxD5Qg" + }, + { + "name": "Accept-Encoding", + "value": "gzip" + } + ], + "queryString": [], + "postData": { + "mimeType": "application/json; charset=UTF-8", + "text": "{\"foo1\":\"Kie7p\",\"foo2\":12.3}" + }, + "headersSize": 271, + "bodySize": 28 + }, + "response": { + "_charlesStatus": "COMPLETE", + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [ + { + "name": "sails.sid", + "value": "s%3A3unIFKLK0Hl_B2pdTkPkT7EfMFT-Haj4.IP8%2BW82GuC6rgKB3ftSTzeIuU06XtsL9MfK7MVk3ADE", + "path": "/", + "domain": null, + "expires": null, + "httpOnly": true, + "secure": false, + "comment": null, + "_maxAge": null + } + ], + "headers": [ + { + "name": "Date", + "value": "Mon, 11 Oct 2021 12:33:09 GMT" + }, + { + "name": "Content-Type", + "value": "application/json; charset=utf-8" + }, + { + "name": "Content-Length", + "value": "528" + }, + { + "name": "ETag", + "value": "W/\"210-jFdmdcCipJ2EiYEjcIabSk/psPI\"" + }, + { + "name": "Vary", + "value": "Accept-Encoding" + }, + { + "name": "set-cookie", + "value": "sails.sid=s%3A3unIFKLK0Hl_B2pdTkPkT7EfMFT-Haj4.IP8%2BW82GuC6rgKB3ftSTzeIuU06XtsL9MfK7MVk3ADE; Path=/; HttpOnly" + }, + { + "name": "Connection", + "value": "keep-alive" + } + ], + "content": { + "size": 528, + "mimeType": "application/json; charset=utf-8", + "text": "eyJhcmdzIjp7fSwiZGF0YSI6eyJmb28xIjoiS2llN3AiLCJmb28yIjoxMi4zfSwiZmlsZXMiOnt9LCJmb3JtIjp7fSwiaGVhZGVycyI6eyJ4LWZvcndhcmRlZC1wcm90byI6Imh0dHBzIiwieC1mb3J3YXJkZWQtcG9ydCI6IjQ0MyIsImhvc3QiOiJwb3N0bWFuLWVjaG8uY29tIiwieC1hbXpuLXRyYWNlLWlkIjoiUm9vdD0xLTYxNjQyZjA1LTExMTZkZmZlN2U0YTQ5ODg0NDBjMDI2NiIsImNvbnRlbnQtbGVuZ3RoIjoiMjgiLCJ1c2VyLWFnZW50IjoiR28taHR0cC1jbGllbnQvMS4xIiwiY29udGVudC10eXBlIjoiYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD1VVEYtOCIsImNvb2tpZSI6InNhaWxzLnNpZD1zJTNBT2Y1ZUtzMzZlRDh4SWo1bGlvMDl6WHJoVnJhMXlQSG8uUEp5UVl4b3lQdlhOVVBhbDRUajBPYUpETERKN3RaZlolMkJDTDM3WHhENVFnIiwiYWNjZXB0LWVuY29kaW5nIjoiZ3ppcCJ9LCJqc29uIjp7ImZvbzEiOiJLaWU3cCIsImZvbzIiOjEyLjN9LCJ1cmwiOiJodHRwczovL3Bvc3RtYW4tZWNoby5jb20vcG9zdCJ9", + "encoding": "base64" + }, + "redirectURL": null, + "headersSize": 0, + "bodySize": 528 + }, + "serverIPAddress": "54.209.48.16", + "cache": {}, + "timings": { + "dns": -1, + "connect": -1, + "ssl": -1, + "send": 0, + "wait": 272, + "receive": 1 + } + } + ] + } +} \ No newline at end of file diff --git a/har2case/core.go b/har2case/core.go index c154a494..269e0314 100644 --- a/har2case/core.go +++ b/har2case/core.go @@ -1,7 +1,14 @@ package har2case import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/url" + "os" "path/filepath" + "strings" "github.com/httprunner/httpboomer" ) @@ -21,7 +28,10 @@ type HAR struct { func (h *HAR) GenJSON() (jsonPath string, err error) { jsonPath = getFilenameWithoutExtension(h.path) + ".json" - tCase := h.makeTestCase() + tCase, err := h.makeTestCase() + if err != nil { + return "", err + } err = tCase.Dump2JSON(jsonPath) return } @@ -29,16 +39,46 @@ func (h *HAR) GenJSON() (jsonPath string, err error) { func (h *HAR) GenYAML() (yamlPath string, err error) { yamlPath = getFilenameWithoutExtension(h.path) + ".yaml" - tCase := h.makeTestCase() + tCase, err := h.makeTestCase() + if err != nil { + return "", err + } err = tCase.Dump2YAML(yamlPath) return } -func (h *HAR) makeTestCase() *httpboomer.TCase { - return &httpboomer.TCase{ - Config: *h.prepareConfig(), - TestSteps: h.prepareTestSteps(), +func (h *HAR) makeTestCase() (*httpboomer.TCase, error) { + teststeps, err := h.prepareTestSteps() + if err != nil { + return nil, err } + + tCase := &httpboomer.TCase{ + Config: *h.prepareConfig(), + TestSteps: teststeps, + } + return tCase, nil +} + +func (h *HAR) load() (*Har, error) { + fp, err := os.Open(h.path) + if err != nil { + return nil, fmt.Errorf("open: %w", err) + } + + data, err := ioutil.ReadAll(fp) + fp.Close() + if err != nil { + return nil, fmt.Errorf("read: %w", err) + } + + har := &Har{} + err = json.Unmarshal(data, har) + if err != nil { + return nil, fmt.Errorf("json.Unmarshal error: %w", err) + } + + return har, nil } func (h *HAR) prepareConfig() *httpboomer.TConfig { @@ -49,9 +89,122 @@ func (h *HAR) prepareConfig() *httpboomer.TConfig { } } -func (h *HAR) prepareTestSteps() []*httpboomer.TStep { +func (h *HAR) prepareTestSteps() ([]*httpboomer.TStep, error) { + har, err := h.load() + if err != nil { + return nil, err + } + var steps []*httpboomer.TStep - return steps + for _, entry := range har.Log.Entries { + step, err := h.prepareTestStep(&entry) + if err != nil { + return nil, err + } + steps = append(steps, step) + } + + return steps, nil +} + +func (h *HAR) prepareTestStep(entry *Entry) (*httpboomer.TStep, error) { + tStep := &TStep{ + TStep: httpboomer.TStep{ + Request: &httpboomer.TRequest{}, + Validators: make([]httpboomer.TValidator, 0), + }, + } + if err := tStep.makeRequestMethod(entry); err != nil { + return nil, err + } + if err := tStep.makeRequestURL(entry); err != nil { + return nil, err + } + if err := tStep.makeRequestParams(entry); err != nil { + return nil, err + } + if err := tStep.makeRequestCookies(entry); err != nil { + return nil, err + } + if err := tStep.makeRequestHeaders(entry); err != nil { + return nil, err + } + if err := tStep.makeRequestBody(entry); err != nil { + return nil, err + } + if err := tStep.makeValidate(); err != nil { + return nil, err + } + return &tStep.TStep, nil +} + +type TStep struct { + httpboomer.TStep +} + +func (s *TStep) makeRequestMethod(entry *Entry) error { + s.Request.Method = httpboomer.EnumHTTPMethod(entry.Request.Method) + return nil +} + +func (s *TStep) makeRequestURL(entry *Entry) error { + + u, err := url.Parse(entry.Request.URL) + if err != nil { + log.Printf("makeRequestURL error: %v", err) + return err + } + s.Request.URL = fmt.Sprintf("%s://%s", u.Scheme, u.Hostname()+u.Path) + return nil +} + +func (s *TStep) makeRequestParams(entry *Entry) error { + s.Request.Params = make(map[string]interface{}) + for _, param := range entry.Request.QueryString { + s.Request.Params[param.Name] = param.Value + } + return nil +} + +func (s *TStep) makeRequestCookies(entry *Entry) error { + s.Request.Cookies = make(map[string]string) + for _, cookie := range entry.Request.Cookies { + s.Request.Cookies[cookie.Name] = cookie.Value + } + return nil +} + +func (s *TStep) makeRequestHeaders(entry *Entry) error { + s.Request.Headers = make(map[string]string) + for _, header := range entry.Request.Headers { + if strings.EqualFold(header.Name, "cookie") { + continue + } + s.Request.Headers[header.Name] = header.Value + } + return nil +} + +func (s *TStep) makeRequestBody(entry *Entry) error { + mimeType := entry.Request.PostData.MimeType + if strings.HasPrefix(mimeType, "application/json") { + // post json + var data interface{} + err := json.Unmarshal([]byte(entry.Request.PostData.Text), &data) + if err != nil { + log.Printf("makeRequestBody error: %v", err) + return err + } + s.Request.Data = data + } else if strings.HasPrefix(mimeType, "application/x-www-form-urlencoded") { + // TODO: post form data + } + return nil +} + +func (s *TStep) makeValidate() error { + s.Validators = nil + return nil } func getFilenameWithoutExtension(path string) string { diff --git a/har2case/core_test.go b/har2case/core_test.go index aaf6205e..4ff58d26 100644 --- a/har2case/core_test.go +++ b/har2case/core_test.go @@ -4,12 +4,14 @@ import ( "log" "os" "testing" + + "github.com/stretchr/testify/assert" ) var harPath string func TestMain(m *testing.M) { - harPath = "demo.har" + harPath = "../examples/demo.har" // run all tests code := m.Run() @@ -21,4 +23,71 @@ func TestMain(m *testing.M) { func TestGenJSON(t *testing.T) { jsonPath, err := NewHAR(harPath).GenJSON() log.Printf("jsonPath: %v, err: %v", jsonPath, err) + if !assert.NoError(t, err) { + t.Fail() + } + if !assert.NotEmpty(t, jsonPath) { + t.Fail() + } +} + +func TestLoadHAR(t *testing.T) { + har := NewHAR(harPath) + h, err := har.load() + if !assert.NoError(t, err) { + t.Fail() + } + if !assert.Equal(t, "GET", h.Log.Entries[0].Request.Method) { + t.Fail() + } + if !assert.Equal(t, "POST", h.Log.Entries[1].Request.Method) { + t.Fail() + } +} + +func TestMakeTestCase(t *testing.T) { + har := NewHAR(harPath) + tCase, err := har.makeTestCase() + if !assert.NoError(t, err) { + t.Fail() + } + + // make request method + if !assert.EqualValues(t, "GET", tCase.TestSteps[0].Request.Method) { + t.Fail() + } + if !assert.EqualValues(t, "POST", tCase.TestSteps[1].Request.Method) { + t.Fail() + } + + // make request url + if !assert.Equal(t, "https://postman-echo.com/get", tCase.TestSteps[0].Request.URL) { + t.Fail() + } + if !assert.Equal(t, "https://postman-echo.com/post", tCase.TestSteps[1].Request.URL) { + t.Fail() + } + + // make request params + if !assert.Equal(t, "Kie7p", tCase.TestSteps[0].Request.Params["foo1"]) { + t.Fail() + } + + // make request cookies + if !assert.NotEmpty(t, tCase.TestSteps[1].Request.Cookies["sails.sid"]) { + t.Fail() + } + + // make request headers + if !assert.Equal(t, "HttpBoomer", tCase.TestSteps[0].Request.Headers["User-Agent"]) { + t.Fail() + } + if !assert.Equal(t, "postman-echo.com", tCase.TestSteps[0].Request.Headers["Host"]) { + t.Fail() + } + + // make request data + if !assert.Equal(t, map[string]interface{}{"foo1": "Kie7p", "foo2": 12.3}, tCase.TestSteps[1].Request.Data) { + t.Fail() + } } diff --git a/har2case/har.go b/har2case/har.go new file mode 100644 index 00000000..6b98839a --- /dev/null +++ b/har2case/har.go @@ -0,0 +1,340 @@ +package har2case + +import "time" + +/* +HTTP Archive (HAR) format +https://w3c.github.io/web-performance/specs/HAR/Overview.html +this file is copied from https://github.com/mrichman/hargo/blob/master/types.go +*/ + +// Har is a container type for deserialization +type Har struct { + Log Log `json:"log"` +} + +// Log represents the root of the exported data. This object MUST be present and its name MUST be "log". +type Log struct { + // The object contains the following name/value pairs: + + // Required. Version number of the format. + Version string `json:"version"` + // Required. An object of type creator that contains the name and version + // information of the log creator application. + Creator Creator `json:"creator"` + // Optional. An object of type browser that contains the name and version + // information of the user agent. + Browser Browser `json:"browser"` + // Optional. An array of objects of type page, each representing one exported + // (tracked) page. Leave out this field if the application does not support + // grouping by pages. + Pages []Page `json:"pages,omitempty"` + // Required. An array of objects of type entry, each representing one + // exported (tracked) HTTP request. + Entries []Entry `json:"entries"` + // Optional. A comment provided by the user or the application. Sorting + // entries by startedDateTime (starting from the oldest) is preferred way how + // to export data since it can make importing faster. However the reader + // application should always make sure the array is sorted (if required for + // the import). + Comment string `json:"comment"` +} + +// Creator contains information about the log creator application +type Creator struct { + // Required. The name of the application that created the log. + Name string `json:"name"` + // Required. The version number of the application that created the log. + Version string `json:"version"` + // Optional. A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Browser that created the log +type Browser struct { + // Required. The name of the browser that created the log. + Name string `json:"name"` + // Required. The version number of the browser that created the log. + Version string `json:"version"` + // Optional. A comment provided by the user or the browser. + Comment string `json:"comment"` +} + +// Page object for every exported web page and one object for every HTTP request. +// In case when an HTTP trace tool isn't able to group requests by a page, +// the object is empty and individual requests doesn't have a parent page. +type Page struct { + /* There is one object for every exported web page and one + object for every HTTP request. In case when an HTTP trace tool isn't able to + group requests by a page, the object is empty and individual + requests doesn't have a parent page. + */ + + // Date and time stamp for the beginning of the page load + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.45+01:00). + StartedDateTime string `json:"startedDateTime"` + // Unique identifier of a page within the . Entries use it to refer the parent page. + ID string `json:"id"` + // Page title. + Title string `json:"title"` + // Detailed timing info about page load. + PageTiming PageTiming `json:"pageTiming"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// PageTiming describes timings for various events (states) fired during the page load. +// All times are specified in milliseconds. If a time info is not available appropriate field is set to -1. +type PageTiming struct { + // Content of the page loaded. Number of milliseconds since page load started + // (page.startedDateTime). Use -1 if the timing does not apply to the current + // request. + // Depeding on the browser, onContentLoad property represents DOMContentLoad + // event or document.readyState == interactive. + OnContentLoad int `json:"onContentLoad"` + // Page is loaded (onLoad event fired). Number of milliseconds since page + // load started (page.startedDateTime). Use -1 if the timing does not apply + // to the current request. + OnLoad int `json:"onLoad"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment"` +} + +// Entry is a unique, optional Reference to the parent page. +// Leave out this field if the application does not support grouping by pages. +type Entry struct { + Pageref string `json:"pageref,omitempty"` + // Date and time stamp of the request start + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD). + StartedDateTime string `json:"startedDateTime"` + // Total elapsed time of the request in milliseconds. This is the sum of all + // timings available in the timings object (i.e. not including -1 values) . + Time float32 `json:"time"` + // Detailed info about the request. + Request Request `json:"request"` + // Detailed info about the response. + Response Response `json:"response"` + // Info about cache usage. + Cache Cache `json:"cache"` + // Detailed timing info about request/response round trip. + PageTimings PageTimings `json:"pageTimings"` + // optional (new in 1.2) IP address of the server that was connected + // (result of DNS resolution). + ServerIPAddress string `json:"serverIPAddress,omitempty"` + // optional (new in 1.2) Unique ID of the parent TCP/IP connection, can be + // the client port number. Note that a port number doesn't have to be unique + // identifier in cases where the port is shared for more connections. If the + // port isn't available for the application, any other unique connection ID + // can be used instead (e.g. connection index). Leave out this field if the + // application doesn't support this info. + Connection string `json:"connection,omitempty"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Request contains detailed info about performed request. +type Request struct { + // Request method (GET, POST, ...). + Method string `json:"method"` + // Absolute URL of the request (fragments are not included). + URL string `json:"url"` + // Request HTTP Version. + HTTPVersion string `json:"httpVersion"` + // List of cookie objects. + Cookies []Cookie `json:"cookies"` + // List of header objects. + Headers []NVP `json:"headers"` + // List of query parameter objects. + QueryString []NVP `json:"queryString"` + // Posted data. + PostData PostData `json:"postData"` + // Total number of bytes from the start of the HTTP request message until + // (and including) the double CRLF before the body. Set to -1 if the info + // is not available. + HeaderSize int `json:"headerSize"` + // Size of the request body (POST data payload) in bytes. Set to -1 if the + // info is not available. + BodySize int `json:"bodySize"` + // (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment"` +} + +// Response contains detailed info about the response. +type Response struct { + // Response status. + Status int `json:"status"` + // Response status description. + StatusText string `json:"statusText"` + // Response HTTP Version. + HTTPVersion string `json:"httpVersion"` + // List of cookie objects. + Cookies []Cookie `json:"cookies"` + // List of header objects. + Headers []NVP `json:"headers"` + // Details about the response body. + Content Content `json:"content"` + // Redirection target URL from the Location response header. + RedirectURL string `json:"redirectURL"` + // Total number of bytes from the start of the HTTP response message until + // (and including) the double CRLF before the body. Set to -1 if the info is + // not available. + // The size of received response-headers is computed only from headers that + // are really received from the server. Additional headers appended by the + // browser are not included in this number, but they appear in the list of + // header objects. + HeadersSize int `json:"headersSize"` + // Size of the received response body in bytes. Set to zero in case of + // responses coming from the cache (304). Set to -1 if the info is not + // available. + BodySize int `json:"bodySize"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Cookie contains list of all cookies (used in and objects). +type Cookie struct { + // The name of the cookie. + Name string `json:"name"` + // The cookie value. + Value string `json:"value"` + // optional The path pertaining to the cookie. + Path string `json:"path,omitempty"` + // optional The host of the cookie. + Domain string `json:"domain,omitempty"` + // optional Cookie expiration time. + // (ISO 8601 YYYY-MM-DDThh:mm:ss.sTZD, e.g. 2009-07-24T19:20:30.123+02:00). + Expires string `json:"expires,omitempty"` + // optional Set to true if the cookie is HTTP only, false otherwise. + HTTPOnly bool `json:"httpOnly,omitempty"` + // optional (new in 1.2) True if the cookie was transmitted over ssl, false + // otherwise. + Secure bool `json:"secure,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment bool `json:"comment,omitempty"` +} + +// NVP is simply a name/value pair with a comment +type NVP struct { + Name string `json:"name"` + Value string `json:"value"` + Comment string `json:"comment,omitempty"` +} + +// PostData describes posted data, if any (embedded in object). +type PostData struct { + // Mime type of posted data. + MimeType string `json:"mimeType"` + // List of posted parameters (in case of URL encoded parameters). + Params []PostParam `json:"params"` + // Plain text posted data + Text string `json:"text"` + // optional (new in 1.2) A comment provided by the user or the + // application. + Comment string `json:"comment,omitempty"` +} + +// PostParam is a list of posted parameters, if any (embedded in object). +type PostParam struct { + // name of a posted parameter. + Name string `json:"name"` + // optional value of a posted parameter or content of a posted file. + Value string `json:"value,omitempty"` + // optional name of a posted file. + FileName string `json:"fileName,omitempty"` + // optional content type of a posted file. + ContentType string `json:"contentType,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Content describes details about response content (embedded in object). +type Content struct { + // Length of the returned content in bytes. Should be equal to + // response.bodySize if there is no compression and bigger when the content + // has been compressed. + Size int `json:"size"` + // optional Number of bytes saved. Leave out this field if the information + // is not available. + Compression int `json:"compression,omitempty"` + // MIME type of the response text (value of the Content-Type response + // header). The charset attribute of the MIME type is included (if + // available). + MimeType string `json:"mimeType"` + // optional Response body sent from the server or loaded from the browser + // cache. This field is populated with textual content only. The text field + // is either HTTP decoded text or a encoded (e.g. "base64") representation of + // the response body. Leave out this field if the information is not + // available. + Text string `json:"text,omitempty"` + // optional (new in 1.2) Encoding used for response text field e.g + // "base64". Leave out this field if the text field is HTTP decoded + // (decompressed & unchunked), than trans-coded from its original character + // set into UTF-8. + Encoding string `json:"encoding,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// Cache contains info about a request coming from browser cache. +type Cache struct { + // optional State of a cache entry before the request. Leave out this field + // if the information is not available. + BeforeRequest CacheObject `json:"beforeRequest,omitempty"` + // optional State of a cache entry after the request. Leave out this field if + // the information is not available. + AfterRequest CacheObject `json:"afterRequest,omitempty"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// CacheObject is used by both beforeRequest and afterRequest +type CacheObject struct { + // optional - Expiration time of the cache entry. + Expires string `json:"expires,omitempty"` + // The last time the cache entry was opened. + LastAccess string `json:"lastAccess"` + // Etag + ETag string `json:"eTag"` + // The number of times the cache entry has been opened. + HitCount int `json:"hitCount"` + // optional (new in 1.2) A comment provided by the user or the application. + Comment string `json:"comment,omitempty"` +} + +// PageTimings describes various phases within request-response round trip. +// All times are specified in milliseconds. +type PageTimings struct { + Blocked int `json:"blocked,omitempty"` + // optional - Time spent in a queue waiting for a network connection. Use -1 + // if the timing does not apply to the current request. + DNS int `json:"dns,omitempty"` + // optional - DNS resolution time. The time required to resolve a host name. + // Use -1 if the timing does not apply to the current request. + Connect int `json:"connect,omitempty"` + // optional - Time required to create TCP connection. Use -1 if the timing + // does not apply to the current request. + Send int `json:"send"` + // Time required to send HTTP request to the server. + Wait int `json:"wait"` + // Waiting for a response from the server. + Receive int `json:"receive"` + // Time required to read entire response from the server (or cache). + Ssl int `json:"ssl,omitempty"` + // optional (new in 1.2) - Time required for SSL/TLS negotiation. If this + // field is defined then the time is also included in the connect field (to + // ensure backward compatibility with HAR 1.1). Use -1 if the timing does not + // apply to the current request. + Comment string `json:"comment,omitempty"` + // optional (new in 1.2) - A comment provided by the user or the application. +} + +// TestResult contains results for an individual HTTP request +type TestResult struct { + URL string `json:"url"` + Status int `json:"status"` // 200, 500, etc. + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Latency int `json:"latency"` // milliseconds + Method string `json:"method"` + HarFile string `json:"harfile"` +} diff --git a/httpboomer/README.md b/httpboomer/README.md new file mode 100644 index 00000000..5852febb --- /dev/null +++ b/httpboomer/README.md @@ -0,0 +1 @@ +# httpboomer cli diff --git a/models.go b/models.go index 11268c41..d7b078bb 100644 --- a/models.go +++ b/models.go @@ -1,15 +1,15 @@ package httpboomer -type enumHTTPMethod string +type EnumHTTPMethod string const ( - GET enumHTTPMethod = "GET" - HEAD enumHTTPMethod = "HEAD" - POST enumHTTPMethod = "POST" - PUT enumHTTPMethod = "PUT" - DELETE enumHTTPMethod = "DELETE" - OPTIONS enumHTTPMethod = "OPTIONS" - PATCH enumHTTPMethod = "PATCH" + GET EnumHTTPMethod = "GET" + HEAD EnumHTTPMethod = "HEAD" + POST EnumHTTPMethod = "POST" + PUT EnumHTTPMethod = "PUT" + DELETE EnumHTTPMethod = "DELETE" + OPTIONS EnumHTTPMethod = "OPTIONS" + PATCH EnumHTTPMethod = "PATCH" ) type TConfig struct { @@ -23,7 +23,7 @@ type TConfig struct { } type TRequest struct { - Method enumHTTPMethod `json:"method" yaml:"method"` + Method EnumHTTPMethod `json:"method" yaml:"method"` URL string `json:"url" yaml:"url"` Params map[string]interface{} `json:"params,omitempty" yaml:"params,omitempty"` Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"`