Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for Server Side events #181

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Built with Go - Mmock runs without installation on multiple platforms.
* Public interface auto discover
* Lightweight and portable
* No installation required
* Server Side Events

### Example

Expand Down Expand Up @@ -236,7 +237,7 @@ See https://pkg.go.dev/regexp/syntax for regexp syntax
* *statusCode*: Response status code
* *headers*: Array of headers. It allows more than one value for the same key and vars.
* *cookies*: Array of cookies. It allows vars.
* *body*: Body string. It allows vars.
* *body*: Body string. It allows vars. For SSE, pass the body in array of JSON format.

#### Callback (Optional)

Expand Down Expand Up @@ -631,6 +632,8 @@ You can always disable this behavior adding the following flag `-server-statisti
- Improved logging with levels thanks to [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for Regular Expressions for QueryStringParameters [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for URI and Description tags [@jcdietrich](https://github.com/jcdietrich) [@jdietrich-tc](https://github.com/jdietrich-tc)
- Support for Server Side Events [@rosspatil](https://github.com/rosspatil)


### Contributing

Expand Down
42 changes: 42 additions & 0 deletions config/sse.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
request:
method: POST
path: /events
response:
statusCode: 200
headers:
Content-Type:
- "text/event-stream"
Connection:
- "keep-alive"
Cache-Control:
- "no-cache"
body: >
[
{
"test":"1"
},
{
"test":"2"
},
{
"test":"3"
},
{
"test":"4"
},
{
"test":"5"
},
{
"test":"6"
},
{
"test":"7"
},
{
"test":"8"
},
{
"test":"9"
}
]
2 changes: 1 addition & 1 deletion internal/server/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (di *Dispatcher) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}

//translate request
di.Translator.WriteHTTPResponseFromDefinition(transaction.Response, w)
di.Translator.WriteHTTPResponseFromDefinition(transaction.Response, w, req)

if mock.Callback.Url != "" {
go func() {
Expand Down
49 changes: 43 additions & 6 deletions pkg/mock/http.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package mock

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"

"github.com/tidwall/gjson"
)

// HTTP is and adaptor beteewn the http and mock config.
Expand Down Expand Up @@ -39,7 +42,7 @@ func (t HTTP) BuildRequestDefinitionFromHTTP(req *http.Request) Request {
res.QueryStringParameters[name] = values
}

body, _ := ioutil.ReadAll(req.Body)
body, _ := io.ReadAll(req.Body)
res.Body = string(body)

return res
Expand Down Expand Up @@ -68,22 +71,56 @@ func getHostAndPort(req *http.Request) (string, string) {
}

// WriteHTTPResponseFromDefinition read a mock response and write a http response.
func (t HTTP) WriteHTTPResponseFromDefinition(fr *Response, w http.ResponseWriter) {
func (t HTTP) WriteHTTPResponseFromDefinition(fr *Response, w http.ResponseWriter, req *http.Request) {
if isSSE(fr) {
streamResponse(fr, w, req)
return
}
addHeadersAndCookies(fr, w)
w.WriteHeader(fr.StatusCode)
io.WriteString(w, fr.Body)
}

// Check if the response is of type SSE
func isSSE(fr *Response) bool {
values, ok := fr.Headers["content-type"]
if ok {
for _, value := range values {
return strings.ToLower(value) == "text/event-stream"
}
}
return false
}

func addHeadersAndCookies(fr *Response, w http.ResponseWriter) {
for header, values := range fr.Headers {
for _, value := range values {
w.Header().Add(header, value)
}

}

if len(fr.Cookies) > 0 {
cookies := []string{}
for cookie, value := range fr.Cookies {
cookies = append(cookies, fmt.Sprintf("%s=%s", cookie, value))
}
w.Header().Add("Set-Cookie", strings.Join(cookies, ";"))
}
}

w.WriteHeader(fr.StatusCode)
io.WriteString(w, fr.Body)
// streamResponse - stream response
func streamResponse(fr *Response, w http.ResponseWriter, req *http.Request) {
addHeadersAndCookies(fr, w)

for _, response := range gjson.Parse(fr.Body).Array() {
time.Sleep(time.Second * 2)
select {
case <-req.Context().Done():
return
default:
ba, _ := json.Marshal((response.Value()))
fmt.Fprintf(w, "data: %s\n\n", string(ba))
w.(http.Flusher).Flush()
}
}
}
2 changes: 1 addition & 1 deletion pkg/mock/message_translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type MockRequestBuilder interface {

// MockResponseWriter defines the translator from config.Response to http.ResponseWriter
type MockResponseWriter interface {
WriteHTTPResponseFromDefinition(fr *Response, w http.ResponseWriter)
WriteHTTPResponseFromDefinition(fr *Response, w http.ResponseWriter, req *http.Request)
}

// MessageTranslator defines the translator contract between http and mock and viceversa.
Expand Down
Loading