gavv / httpexpect

End-to-end HTTP and REST API testing for Go.
https://pkg.go.dev/github.com/gavv/httpexpect/v2
MIT License
2.54k stars 239 forks source link

Implement EventSource support #246

Closed gavv closed 1 year ago

gavv commented 1 year ago

This issue depends on two other issues:

UPDATE: both of them are completed now.


Add support for EventSource, a.k.a. Event Stream, a.k.a. SSE (Server-Sent Events).

It was requested a while ago here: https://github.com/gavv/httpexpect/discussions/145

We already have support for websockets, and support for SSE will have similar design, but simpler:


Decoding of SSE events can be implemented using this package: https://github.com/alevinval/sse. It provides Decoder struct, which can read events from http.Response body.

New methods and structs should be covered with unit tests. In addition, we need a new e2e (end to end) test, similar to e2e_websocket_test.go.

We also need a new example similar to _examples/websocket.go. To make sure that our SSE implementation is compatible with spec, it is suggested to use another package for SSE server in that example. A good candidate is https://github.com/r3labs/sse.


Wiki: https://en.wikipedia.org/wiki/Server-sent_events Spec: https://html.spec.whatwg.org/multipage/server-sent-events.html

g41797 commented 1 year ago

As far as I understand it's not urgent issue I just started my open source journey and pretty sure, that this feature will take some time Pros: no one depend on additional question - according to .gitignore, goland is used as ide i am working with vscode

"Waiting for reply" :-)

gavv commented 1 year ago

You're welcome!

g41797 commented 1 year ago

for further reading: https://en.wikipedia.org/wiki/Server-sent_events

https://blog.axway.com/learning-center/apis/api-streaming/server-sent-events Event: The event’s type. It will allow you to use the same stream for different content. A client can decide to “listen” only to one type of event or to interpret differently each event type.[Additional param?] Retry: The time to use before the browser attempts a new connection ...[???]

http://html5doctor.com/server-sent-events/

https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events

https://html.spec.whatwg.org/multipage/server-sent-events.html

source = new EventSource( url [, { withCredentials: true } ])

withCredentials - check reqs.

Last-Event-ID header - check usage

====>

https://shopify.engineering/server-sent-events-data-streaming https://meronhayle.me/implementing-sse-using-go/ https://thedevelopercafe.com/articles/server-sent-events-in-go-595ae2740c7a

https://bigboxcode.com/go-server-sent-events-sse https://copyprogramming.com/howto/server-sent-events-golang https://www.timeplus.com/post/websocket-vs-sse

https://www.reddit.com/r/golang/comments/pm71iq/fully_featured_speccompliant_html5_serversent/ https://go-gin.onrender.com/room/hn

https://docs.servicestack.net/server-events

https://github.com/grciuta/sse-golang-example

g41797 commented 1 year ago

go web servers for dummies: https://www.dudley.codes/posts https://pace.dev/blog/2018/05/09/how-I-write-http-services-after-eight-years.html https://www.alexedwards.net/blog/the-fat-service-pattern

--> https://threedots.tech/post/common-anti-patterns-in-go-web-applications/

https://medium.com/nerd-for-tech/golang-build-a-simple-web-server-and-interact-with-it-689ee0f4d1de https://chidiwilliams.com/post/writing-cleaner-go-web-servers/ https://drstearns.github.io/tutorials/gomiddleware/ https://www.geeksforgeeks.org/how-to-build-a-simple-web-server-with-golang/ https://thecodeway.hashnode.dev/building-a-lightweight-web-server-with-golang https://html.spec.whatwg.org/multipage/server-sent-events.html


https://www.openapis.org idl

g41797 commented 1 year ago
                    EventSource vs SSEClient

Usage of HTML5 standard interface name causes confusion

interface EventSource : EventTarget { constructor(USVString url, optional EventSourceInit eventSourceInitDict = {});

readonly attribute USVString url; readonly attribute boolean withCredentials;

// ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSED = 2; readonly attribute unsigned short readyState;

// networking attribute EventHandler onopen; attribute EventHandler onmessage; attribute EventHandler onerror; undefined close(); };

dictionary EventSourceInit { boolean withCredentials = false; };

SSEClient struct represents SSE connection. It should have methods:

EventSource should be interface - specific implementation depends on used SSE/HHTP package. This approach allows to use different client/server SSE/HTTP implementations.

For websocket: we use gorilla/websocket internally

For SSE: Internal implementation of EventSource based on https://github.com/r3labs/sse It may be replaced with custom EventSource

Response will have new method SSEClient etc....

httpexpect structs ===> SSEClient - - - > EventSource (visible only for SSEClient)

g41797 commented 1 year ago
   Usage of 3rd party SSE  vs own implementation

3rd party (e.g. https://github.com/r3labs/sse)

    • shorter dev. time [+]
    • more stable code - supported by community [+]
    • lack access to http header etc. for testing of low-level info [-]

Need clarification what's E2E testing for SEE:

For app layer usage of 3rd party is preferable choice

g41797 commented 1 year ago
             From github.com/r3labs/sse/v2

type Event struct { timestamp time.Time ID []byte Data []byte Event []byte Retry []byte Comment []byte }

From HTTP5 : Event handler Event
onopen "open"
onmessage "message"
onerror "error"

Check events supported by sse

g41797 commented 1 year ago

Regarding wsUpgrade bool (see WithWebsocketUpgrade)

Should be replaced with something like:

type clientType int

const ( typeRegular clientType = iota // HTTP client typeWebSocket // WebSocket typeEventSource // EventSource (Server Sent Event) )

Consider to add this field also to responseOpts (used for creation of response)

g41797 commented 1 year ago

What about shortcut:

Request will have new method EventSource() (similar to Response.Websocket). It should check TBD , create a new instance of EventSource struct.

EventSource struct (similar to Websocket struct) represents SSE connection. It should have methods:

[Note: How to retrieve unknown in advance number of events without test failure?]

Event struct (similar to WebsocketMessage struct) represents a single event retrieved from server. It should have methods:

Looks closer to client side flow

I have no problem to implement former design, but before implementation it worth to discuss alternatives

Waiting for reply

g41797 commented 1 year ago

Headers:

According to https://html.spec.whatwg.org/multipage/server-sent-events.html: Set request's cache mode to "no-store". new issue #https://github.com/r3labs/sse/issues/159

Set request's initiator type to "other"

Request.WithEventSource() - validate flow: assert for already called WithEventSource()/WithWebsocketUpgrade()

EventSource methods: called by Request func eventSourceTransform(r *Request) { for k, v := range eventSourceRequestHeaders() { r.withHeader(k, v) } }

called by Response func eventSourceMatch(r *Response) { r.HasContentType("text/event-stream", "UTF-8") r.Status(200) }

g41797 commented 1 year ago

Decoding of SSE events can be implemented using this package: https://github.com/alevinval/sse. It provides Decoder struct, which can read events from http.Response body.

Installation of alevinval/sse - failed Open new issue https://github.com/alevinval/sse/issues/61

Meantime 3 sources from sse were copied to local repo for further development

g41797 commented 1 year ago

Additionally to Expect EventSource will have low-level Read function:

func (es EventSource) Read() (ev Event, err error, timeOut bool){ ...... }

Chans+timeout: https://golangbyexample.com/select-statement-with-timeout-go/ https://stackoverflow.com/questions/49872097/idiomatic-way-for-reading-from-the-channel-for-a-certain-time

Timers: https://gobyexample.com/timers https://stackoverflow.com/questions/50223771/how-to-stop-a-timer-correctly

https://stackoverflow.com/questions/8593645/is-it-ok-to-leave-a-channel-open

g41797 commented 1 year ago

looks this feature has low priority