-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: http mock server and steps (#17)
* feat: http mock server and steps * fix: remove debug tag * fix: remove github errors * feat: clean mock requests * fix: minor improvements in errors format * fix: pull request feedback
- Loading branch information
Showing
26 changed files
with
659 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
|
||
"github.com/Telefonica/golium/mock/http" | ||
) | ||
|
||
func main() { | ||
port := flag.Int("port", 9000, "port for the mock server") | ||
mock := http.NewServer(*port) | ||
log.Fatal(mock.Start()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package http | ||
|
||
import "encoding/json" | ||
|
||
// MockRequest contains the instruction to configure the behaviour of the HTTP mock server. | ||
// The document configures which request is going to be attended (e.g. the path and method) | ||
// and the response to be generated by the mock. | ||
type MockRequest struct { | ||
// Permanent is true if the configuration is permanent. | ||
// If permanent is false, the mockRequest is removed after matching the first request. | ||
Permanent bool `json:"permanent"` | ||
Request Request `json:"request"` | ||
Response Response `json:"response"` | ||
// Latency is the duration in milliseconds to wait to deliver the response. | ||
// If 0, there is no latency to apply. | ||
// If negative, there will be no response (timeout simulation). | ||
Latency int `json:"latency"` | ||
} | ||
|
||
// Request configures the filter for the request of the MockRequest. | ||
type Request struct { | ||
Method string `json:"method,omitempty"` | ||
Path string `json:"path,omitempty"` | ||
Headers map[string][]string `json:"headers,omitempty"` | ||
} | ||
|
||
// Response configures which response if the request filter applies. | ||
type Response struct { | ||
Status int `json:"status"` | ||
Headers map[string][]string `json:"headers"` | ||
Body string `json:"body"` | ||
} | ||
|
||
func (m MockRequest) String() string { | ||
b, _ := json.Marshal(&m) | ||
return string(b) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package http | ||
|
||
import ( | ||
"net/http" | ||
"sync" | ||
|
||
"github.com/Telefonica/golium" | ||
) | ||
|
||
type MockRequests struct { | ||
mockRequests []*MockRequest | ||
mutex sync.Mutex | ||
} | ||
|
||
// PushMockRequest adds a MockRequest to the list. | ||
func (m *MockRequests) PushMockRequest(mockRequest *MockRequest) { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
m.mockRequests = append(m.mockRequests, mockRequest) | ||
} | ||
|
||
// CleanMockRequests removes all the mockRequests from the list. | ||
func (m *MockRequests) CleanMockRequests() { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
m.mockRequests = nil | ||
} | ||
|
||
// MatchMockRequest finds the first mockRequest matching the HTTP request. | ||
func (m *MockRequests) MatchMockRequest(r *http.Request) *MockRequest { | ||
for _, mockRequest := range m.mockRequests { | ||
if matchMockRequest(r, mockRequest) { | ||
return mockRequest | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Remove a mockRequest. It returns true if it was found and removed. | ||
func (m *MockRequests) RemoveMockRequest(mockRequest *MockRequest) bool { | ||
m.mutex.Lock() | ||
defer m.mutex.Unlock() | ||
for i, mr := range m.mockRequests { | ||
if mr != mockRequest { | ||
continue | ||
} | ||
m.mockRequests = append(m.mockRequests[:i], m.mockRequests[i+1:]...) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func matchMockRequest(r *http.Request, mockRequest *MockRequest) bool { | ||
mr := mockRequest.Request | ||
if mr.Method != "" && r.Method != mr.Method { | ||
return false | ||
} | ||
if mr.Path != "" && r.URL.Path != mr.Path { | ||
return false | ||
} | ||
if len(mr.Headers) != 0 { | ||
for header, values := range mr.Headers { | ||
rValues := r.Header[header] | ||
for _, value := range values { | ||
if !golium.ContainsString(value, rValues) { | ||
return false | ||
} | ||
} | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
// Copyright 2021 Telefonica Cybersecurity & Cloud Tech SL | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package http | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// Server type for the HTTP mock server. | ||
type Server struct { | ||
Port int | ||
mockRequests MockRequests | ||
logger *logrus.Entry | ||
} | ||
|
||
// NewServer creates an instance of Server. | ||
func NewServer(port int) *Server { | ||
return &Server{ | ||
Port: port, | ||
mockRequests: MockRequests{}, | ||
logger: logrus.WithField("mock", "http"), | ||
} | ||
} | ||
|
||
// Start the HTTP mock server. | ||
// Note that it blocks the current goroutine with http.ListenAndServe function. | ||
func (s *Server) Start() error { | ||
http.HandleFunc("/_mock/requests", s.handleMockRequest) | ||
http.HandleFunc("/", s.handle) | ||
addr := fmt.Sprintf(":%d", s.Port) | ||
s.logger.Infof("Starting server at '%s'", addr) | ||
return http.ListenAndServe(addr, nil) | ||
} | ||
|
||
func (s *Server) handleMockRequest(w http.ResponseWriter, r *http.Request) { | ||
switch r.Method { | ||
case http.MethodPost: | ||
var mockRequest MockRequest | ||
if err := json.NewDecoder(r.Body).Decode(&mockRequest); err != nil { | ||
s.logger.Errorf("Failed decoding mockRequest: %s", err) | ||
return | ||
} | ||
s.logger.Infof("Pushing mockRequest: %s", mockRequest) | ||
s.mockRequests.PushMockRequest(&mockRequest) | ||
case http.MethodDelete: | ||
s.mockRequests.CleanMockRequests() | ||
default: | ||
w.WriteHeader(http.StatusMethodNotAllowed) | ||
} | ||
} | ||
|
||
func (s *Server) handle(w http.ResponseWriter, r *http.Request) { | ||
mockRequest := s.mockRequests.MatchMockRequest(r) | ||
if mockRequest == nil { | ||
w.WriteHeader(http.StatusNotFound) | ||
return | ||
} | ||
if !mockRequest.Permanent { | ||
s.mockRequests.RemoveMockRequest(mockRequest) | ||
} | ||
if mockRequest.Latency > 0 { | ||
time.Sleep(time.Duration(mockRequest.Latency) * time.Millisecond) | ||
} | ||
resp := mockRequest.Response | ||
for header, values := range resp.Headers { | ||
for _, value := range values { | ||
w.Header().Add(header, value) | ||
} | ||
} | ||
w.WriteHeader(resp.Status) | ||
if _, err := w.Write([]byte(resp.Body)); err != nil { | ||
s.logger.Errorf("Failed writing the response body: %s", err) | ||
} | ||
} |
Oops, something went wrong.