Fix gRPC crash, display gRPC as base64, display gRPC URL and status code (#27)

* Added Method (POST) and URL (emtpy) to gRPC requests.

* Removed quickfix that skips writing HTTP/2 to HAR.

* Use HTTP/2 body to fill out http.Request and htt.Response.

* Make sure that in HARs request.postData.mimeType and response.content.mimeType are application/grpc in case of grpc.

* Comment.

* Add URL and status code for gRPC.

* Don't assume http scheme.

* Use http.Header.Set instead of manually acccessing the underlaying map.
This commit is contained in:
nimrod-up9 2021-05-05 09:23:17 +03:00 committed by GitHub
parent 377fc79315
commit 4d6528771a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 9 deletions

View File

@ -6,8 +6,11 @@ import (
"encoding/base64"
"encoding/binary"
"errors"
"io"
"math"
"net/http"
"net/url"
"strings"
"golang.org/x/net/http2"
"golang.org/x/net/http2/hpack"
@ -62,6 +65,7 @@ func (fbs *fragmentsByStream) pop(streamID uint32) ([]hpack.HeaderField, []byte)
headers := (*fbs)[streamID].headers
data := (*fbs)[streamID].data
delete((*fbs), streamID)
return headers, data
}
@ -100,9 +104,11 @@ func (ga *GrpcAssembler) readMessage() (uint32, interface{}, string, error) {
headers, data := ga.fragmentsByStream.pop(streamID)
// Note: header keys are converted by http.Header.Set to canonical names, e.g. content-type -> Content-Type.
// By converting the keys we violate the HTTP/2 specification, which state that all headers must be lowercase.
headersHTTP1 := make(http.Header)
for _, header := range headers {
headersHTTP1[header.Name] = []string{header.Value}
headersHTTP1.Add(header.Name, header.Value)
}
dataString := base64.StdEncoding.EncodeToString(data)
@ -112,10 +118,14 @@ func (ga *GrpcAssembler) readMessage() (uint32, interface{}, string, error) {
var messageHTTP1 interface{}
if _, ok := headersHTTP1[":method"]; ok {
messageHTTP1 = http.Request{
URL: &url.URL{},
Method: "POST",
Header: headersHTTP1,
Proto: protoHTTP2,
ProtoMajor: protoMajorHTTP2,
ProtoMinor: protoMinorHTTP2,
Body: io.NopCloser(strings.NewReader(dataString)),
ContentLength: int64(len(dataString)),
}
} else if _, ok := headersHTTP1[":status"]; ok {
messageHTTP1 = http.Response{
@ -123,6 +133,8 @@ func (ga *GrpcAssembler) readMessage() (uint32, interface{}, string, error) {
Proto: protoHTTP2,
ProtoMajor: protoMajorHTTP2,
ProtoMinor: protoMinorHTTP2,
Body: io.NopCloser(strings.NewReader(dataString)),
ContentLength: int64(len(dataString)),
}
} else {
return 0, nil, "", errors.New("Failed to assemble stream: neither a request nor a message")

View File

@ -7,6 +7,8 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/google/martian/har"
@ -40,26 +42,41 @@ type HarFile struct {
}
func NewEntry(request *http.Request, requestTime time.Time, response *http.Response, responseTime time.Time) (*har.Entry, error) {
// TODO: quick fix until TRA-3212 is implemented
if request.URL == nil || request.Method == "" {
return nil, errors.New("Invalid request")
}
harRequest, err := har.NewRequest(request, true)
if err != nil {
SilentError("convert-request-to-har", "Failed converting request to HAR %s (%v,%+v)\n", err, err, err)
return nil, errors.New("Failed converting request to HAR")
}
// Martian copies http.Request.URL.String() to har.Request.URL.
// According to the spec, the URL field needs to be the absolute URL.
harRequest.URL = fmt.Sprintf("http://%s%s", request.Host, request.URL)
harResponse, err := har.NewResponse(response, true)
if err != nil {
SilentError("convert-response-to-har", "Failed converting response to HAR %s (%v,%+v)\n", err, err, err)
return nil, errors.New("Failed converting response to HAR")
}
if harRequest.PostData != nil && strings.HasPrefix(harRequest.PostData.MimeType, "application/grpc") {
// Force HTTP/2 gRPC into HAR template
harRequest.URL = fmt.Sprintf("%s://%s%s", request.Header.Get(":scheme"), request.Header.Get(":authority"), request.Header.Get(":path"))
status, err := strconv.Atoi(response.Header.Get(":status"))
if err != nil {
SilentError("convert-response-status-for-har", "Failed converting status to int %s (%v,%+v)\n", err, err, err)
return nil, errors.New("Failed converting response status to int for HAR")
}
harResponse.Status = status
} else {
// Martian copies http.Request.URL.String() to har.Request.URL, which usually contains the path.
// However, according to the HAR spec, the URL field needs to be the absolute URL.
var scheme string
if request.URL.Scheme != "" {
scheme = request.URL.Scheme
} else {
scheme = "http"
}
harRequest.URL = fmt.Sprintf("%s://%s%s", scheme, request.Host, request.URL)
}
totalTime := responseTime.Sub(requestTime).Round(time.Millisecond).Milliseconds()
if totalTime < 1 {
totalTime = 1