github.com/webdestroya/awsmocker@v0.2.6/received_request.go (about) 1 package awsmocker 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "net/http" 9 "regexp" 10 "strings" 11 ) 12 13 var ( 14 credExtractRegexp = regexp.MustCompile(`Credential=(\S+)\b`) 15 jsonRegexp = regexp.MustCompile(`json`) 16 ) 17 18 type ReceivedRequest struct { 19 HttpRequest *http.Request 20 21 Action string 22 Service string 23 Region string 24 25 Hostname string 26 Path string 27 28 // The expected response type based upon the request. JSON requests answered with JSON, 29 // form param posts respond with XML 30 AssumedResponseType string 31 32 // This will only be populated if the request was NOT a form 33 RawBody []byte 34 35 // If the request was a JSON request, then this will be the parsed JSON 36 JsonPayload any 37 38 // TBA: maybe in the future we'll add invalid request flagging, for now allow all types 39 // invalid bool 40 } 41 42 func (rr *ReceivedRequest) Inspect() string { 43 44 if rr.Action != "" && rr.Service != "" { 45 return rr.Service + ":" + rr.Action 46 } 47 48 return fmt.Sprintf("%s %s/%s", rr.HttpRequest.Method, rr.Hostname, rr.Path) 49 } 50 51 func newReceivedRequest(req *http.Request) *ReceivedRequest { 52 recvreq := &ReceivedRequest{ 53 HttpRequest: req, 54 AssumedResponseType: ContentTypeText, 55 Hostname: req.URL.Hostname(), 56 Path: req.URL.Path, 57 } 58 59 _ = req.ParseForm() 60 61 bodyBytes, err := io.ReadAll(req.Body) 62 if err == nil { 63 recvreq.RawBody = bodyBytes 64 } 65 66 reqContentType := req.Header.Get("content-type") 67 if jsonRegexp.MatchString(reqContentType) { 68 recvreq.AssumedResponseType = ContentTypeJSON 69 70 if len(bodyBytes) > 0 { 71 var jsonData any 72 if err := json.Unmarshal(bodyBytes, &jsonData); err == nil { 73 recvreq.JsonPayload = jsonData 74 } 75 } 76 77 } 78 79 authHeader := req.Header.Get("authorization") 80 if authHeader != "" { 81 matches := credExtractRegexp.FindStringSubmatch(authHeader) 82 if len(matches) > 1 { 83 // 0 1 2 3 4 84 // fake/20221030/us-east-1/ecs/aws4_request 85 parts := strings.Split(matches[1], "/") 86 recvreq.Region = parts[2] 87 recvreq.Service = parts[3] 88 } 89 } 90 91 amzTarget := req.Header.Get("x-amz-target") 92 if amzTarget != "" { 93 // X-Amz-Target: AmazonEC2ContainerServiceV20141113.ListClusters 94 _, opname, ok := strings.Cut(amzTarget, ".") 95 if ok { 96 recvreq.Action = opname 97 } 98 } 99 100 if recvreq.Action == "" && req.PostForm.Has("Action") { 101 recvreq.Action = req.PostForm.Get("Action") 102 } 103 104 if recvreq.AssumedResponseType == ContentTypeText && recvreq.Action != "" && recvreq.Service != "" && reqContentType == "application/x-www-form-urlencoded" { 105 recvreq.AssumedResponseType = ContentTypeXML 106 } 107 108 // if recvreq.Action == "" { 109 // log.Println("WARN: Received a request with no action????") 110 // recvreq.invalid = true 111 // return recvreq 112 // } 113 114 return recvreq 115 } 116 117 func (r *ReceivedRequest) DebugDump() { 118 // var buf *bytes.Buffer 119 buf := new(bytes.Buffer) 120 121 fmt.Fprintln(buf, "--- AWSMOCKER RECEIVED REQUEST: -----------------------") 122 123 if r.Action != "" || r.Service != "" { 124 fmt.Fprintf(buf, "Operation: %s (service=%s @ %s)\n", r.Action, r.Service, r.Region) 125 } 126 127 fmt.Fprintf(buf, "%s %s\n", r.HttpRequest.Method, r.HttpRequest.RequestURI) 128 fmt.Fprintf(buf, "Host: %s\n", r.HttpRequest.Host) 129 for k, vlist := range r.HttpRequest.Header { 130 for _, v := range vlist { 131 fmt.Fprintf(buf, "%s: %s\n", k, v) 132 } 133 } 134 135 fmt.Fprintln(buf) 136 137 if len(r.RawBody) > 0 { 138 fmt.Fprintln(buf, "BODY:") 139 fmt.Fprintln(buf, string(r.RawBody)) 140 } else if r.HttpRequest.Form != nil && len(r.HttpRequest.Form) > 0 { 141 fmt.Fprintln(buf, "PARAMS:") 142 for k, vlist := range r.HttpRequest.Form { 143 for _, v := range vlist { 144 fmt.Fprintf(buf, " %s=%s\n", k, v) 145 } 146 } 147 } 148 149 fmt.Fprintln(buf, "-------------------------------------------------------") 150 fmt.Fprintln(buf) 151 152 _, _ = buf.WriteTo(DebugOutputWriter) 153 }