Skip to content

Conversation

@jukkar
Copy link
Member

@jukkar jukkar commented Sep 9, 2019

This PR provides a simple Websocket client API to send Websocket messages to Websocket server. Because the Websocket is established using HTTP, there is small and simple HTTP client API implemented. This HTTP client API can be used to connect to HTTP(S) servers so it is not tied to Websocket client implementation. As this HTTP API is very much needed by Websocket API, I decided not to send it separately to review.

The Websocket API provides functions that can be used by BSD socket API or if more fine grained Websocket support is needed, a Websocket specific API functions are provided.

The main function is this one

int websocket_connect(int http_sock, const char *host, const char *url,
		      const char **optional_headers,
		      websocket_connect_cb_t cb,
		      const struct http_parser_settings *http_cb,
		      u8_t *tmp_buf, size_t tmp_buf_len,
		      s32_t timeout, void *user_data);

It will be given a socket descriptor to the Websocket server. This means that all the non-websocket related things are done outside of this API. The websocket_connect() will connect to the server, do needed HTTP handshakes and return a new websocket socket descriptor that, when used, will add Websocket header to the sent data. User can then either use normal send() or websocket_send_msg() functions to send the data.

One usecase for this API is run MQTT data on top of it. This MQTT support is not part of this PR but to be done separately.

@jukkar jukkar added area: Networking RFC Request For Comments: want input from the community DNM This PR should not be merged (Do Not Merge) labels Sep 9, 2019
@jukkar jukkar added this to the v2.1.0 milestone Sep 9, 2019
@jukkar jukkar requested review from rlubos and rveerama1 September 9, 2019 15:11
@zephyrbot zephyrbot added area: API Changes to public APIs area: Samples Samples labels Sep 9, 2019
@jukkar jukkar force-pushed the websocket-client branch 2 times, most recently from cd2eb4b to 9330e9e Compare September 10, 2019 07:31
@jukkar jukkar requested a review from dbkinder as a code owner September 18, 2019 12:12
@jukkar jukkar removed DNM This PR should not be merged (Do Not Merge) RFC Request For Comments: want input from the community labels Sep 18, 2019
@jukkar
Copy link
Member Author

jukkar commented Sep 18, 2019

Verified that http-client sample works with TLS. Added README files to both samples.

Copy link
Contributor

@rlubos rlubos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone through most of the code, it looks that a lot of work was put into this!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason to use semaphore here instead of mutex?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason for that, just re-using older code that used semaphore.

@jukkar
Copy link
Member Author

jukkar commented Sep 19, 2019

Updated according to comments.

Copy link
Contributor

@dbkinder dbkinder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some suggested edits...

Copy link
Contributor

@dbkinder dbkinder left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for doc changes

@jukkar jukkar force-pushed the websocket-client branch 2 times, most recently from 1e67202 to 023d898 Compare September 25, 2019 09:37
@jukkar
Copy link
Member Author

jukkar commented Sep 25, 2019

Fixed the handling of Sec-Websocket-Accept field in HTTP header.

@ioannisg ioannisg added the Release Notes To be mentioned in the release notes label Sep 26, 2019
Copy link
Contributor

@rlubos rlubos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@pfalcon
Copy link
Contributor

pfalcon commented Oct 1, 2019

Sorry, I was on vacation, then at the conference, so late to the party. Commenting first just based on the textual description and comments here.

The main function is this one

int websocket_connect(WALL OF PARAMETERS);

Didn't we discussed once that having gigantic parameter list is worse than a dedicated structure to capture those params instead? Some params are strange to, e.g.:

        websocket_connect_cb_t cb,

"sockets" and "callbacks" are generally don't mix together. How events are reported for sockets are via pull-style poll() call (instead of push-style callback). If you (for some reason) try to represent high-level websocket communication channel as a system socket, so callbacks, again, look strange.

        const struct http_parser_settings *http_cb,

Why structure of type "http_parser_settings" is named as if it was callback?

const char **optional_headers,

What if I don't have all the headers preallocated as single static array (e.g., don't have enough memory for that)?

all the non-websocket related things are done outside of this API. The websocket_connect() will connect to the server, do needed HTTP handshakes

The first part seems to contradict the second. So, does it issue connect() call? It shouldn't. And if doesn't, then I'd skip mentioning that it "will connect to the server". Mentioning "do needed HTTP handshakes" should be enough, perhaps elaborating it to "will do needed HTTP/Websocket handshake on already established connection".

and return a new websocket socket descriptor that,

If it returns file descriptor, that's our old good friend layering violation. Why conjugate high-level websocket connection to pretend it's a system-level socket. No other (sane) system does that. There're immediate portability implications, e.g. you can't test this websocket code on Linux with comfort it gives, so it's likely has bugs. And if it doesn't try to represent itself as a system-level socket, why return an int, instead of structure pointer?

when used, will add Websocket header to the sent data. User can then either use normal send() or websocket_send_msg() functions to send the data.

Hey, that's cool! But what about received data? ;-)

@pfalcon
Copy link
Contributor

pfalcon commented Oct 1, 2019

Fixed case when we received two websocket packets in same TCP segment, in that case we need to remember the second websocket msg for next socket read.

Websocket is SOCK_STREAM protocol, and SOCK_STREAM sockets don't work in terms of "TCP segments", they work in terms of "streams of bytes". So, if BSD Socket API was properly used to implement websocket code, this error couldn't happen in first place... I'm now a bit afraid to look into the actual code...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier, callback paradigm is counter to native socket paradigm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned earlier, callback paradigm is counter to native socket paradigm.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say http parser settings is implementation detail of HTTP API, not interesting to the user, but who knows...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is do {} while (true), can you make it while (true) {}, because that's the standard idiom for infinite loop.

@jukkar
Copy link
Member Author

jukkar commented Oct 2, 2019

Websocket is SOCK_STREAM protocol, and SOCK_STREAM sockets don't work in terms of "TCP segments", they work in terms of "streams of bytes". So, if BSD Socket API was properly used to implement websocket code, this error couldn't happen in first place... I'm now a bit afraid to look into the actual code...

The Websocket is a message protocol on top of stream protocol (TCP). IMHO it is not very well designed in the first place but we need to cope with this.

@jukkar
Copy link
Member Author

jukkar commented Oct 2, 2019

Didn't we discussed once that having gigantic parameter list is worse than a dedicated structure to capture those params instead? Some params are strange to, e.g.:

Do you have a proposal how to pass the params?

Why structure of type "http_parser_settings" is named as if it was callback?

The struct contains HTTP callback pointers.

What if I don't have all the headers preallocated as single static array (e.g., don't have enough memory for that)?

That is not possible with this API. We would probably need to have a callback to feed extra headers to the stream.

all the non-websocket related things are done outside of this API. The websocket_connect() will connect to the server, do needed HTTP handshakes

The first part seems to contradict the second. So, does it issue connect() call? It shouldn't. And if doesn't, then I'd skip mentioning that it "will connect to the server". Mentioning "do needed HTTP handshakes" should be enough, perhaps elaborating it to "will do needed HTTP/Websocket handshake on already established connection".

There is no contradiction here, user is responsible to call connect(), the websocket API will then connect i.e., establish, like doing handshakes etc., a valid connection to websocket server.

and return a new websocket socket descriptor that,

If it returns file descriptor, that's our old good friend layering violation. Why conjugate high-level websocket connection to pretend it's a system-level socket. No other (sane) system does that. There're immediate portability implications, e.g. you can't test this websocket code on Linux with comfort it gives, so it's likely has bugs. And if it doesn't try to represent itself as a system-level socket, why return an int, instead of structure pointer?

We want to use socket descriptor that is tied to websocket connection. This helps to run things on top of websocket connection and use recv()/send() API. We are not making Linux applications here, and if you want to run valgrind etc you can use native_posix for that.

when used, will add Websocket header to the sent data. User can then either use normal send() or websocket_send_msg() functions to send the data.

Hey, that's cool! But what about received data? ;-)

recv() works just fine too

@jukkar
Copy link
Member Author

jukkar commented Oct 2, 2019

Changing slightly the API proposal:

  • moved most of the function parameters to user given request struct
  • typos fixed
  • added callback that allows user to send the http headers fields itself instead of giving them as pointers

@pfalcon
Copy link
Contributor

pfalcon commented Oct 2, 2019

Do you have a proposal how to pass the params?

Not something specific, beyond moving all (most) params to a struct, and passing a pointer to it to the function. I see that you already pushed something along those lines, let me have a look.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why user_data is passed as a separate param and is not part of struct http_request? (Just in case, I'm asking, not calling for change.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many APIs in networking side etc. have typically timeout and user_data parameters in function calls. Just followed this style here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just followed this style here.

Makes sense.

Copy link
Contributor

@pfalcon pfalcon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed this PR, and agree that it's useful and good deal of effort went into it. I appreciate reacting to some suggestions I've made. I have to say that I'm still not fully satisfied with it due to the fact that HTTP API uses callbacks instead of pull-style, socket-style read()/write() functions (as proposed in #13212). So, I'd give this +0.4, which rounds to +0.

At the same time, I understand why it's done like that - callbacks ultimately come from 3rd-party HTTP protocol parser we use, and "masking" them would just take extra effort, especially if the real task here was implementing Websocket support. The concern with callbacks is their known composability issues, but this very PR somehow counter-proves that, as it was possible to implement socket-style API for websocket on top of the underlying callback-style HTTP API (though it's unclear what issues may be lurking there) . So, overall, I don't think I'd come up with an overall better initial implementation even if I tried a callback-less route.

I definitely hope this will be merged soonish, to allow more people to play with it. In that regard, if my approval would help to merge it sooner rather than later, I'd be happy to give my +1 just for that reason, even though I'm not fully satisfied with the overall design.

Copy link
Member Author

@jukkar jukkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed some typos and minor nits when doing self review. I will fix these.

jukkar added 2 commits October 4, 2019 10:06
Simple HTTP client API.

Signed-off-by: Jukka Rissanen <[email protected]>
Simple HTTP client sample that connects to HTTP server and does
GET and POST requests.

Signed-off-by: Jukka Rissanen <[email protected]>
@jukkar jukkar force-pushed the websocket-client branch 3 times, most recently from 4204720 to 9800b29 Compare October 4, 2019 10:53
jukkar added 3 commits October 4, 2019 13:57
Implement simple API to do Websocket client requests.

Signed-off-by: Jukka Rissanen <[email protected]>
This is BSD sockets based application for connecting to
Websocket server.

Signed-off-by: Jukka Rissanen <[email protected]>
Add "net websocket" command that displays websocket information.

Signed-off-by: Jukka Rissanen <[email protected]>
@jukkar
Copy link
Member Author

jukkar commented Oct 4, 2019

Noticed some fd leaks when closing the connection, those are now fixed.

@jukkar
Copy link
Member Author

jukkar commented Oct 4, 2019

Seems to work ok now. I have been running mqtt_publisher over Websocket without any issues.

@jukkar jukkar merged commit f06fa77 into zephyrproject-rtos:master Oct 4, 2019
@jukkar jukkar deleted the websocket-client branch October 4, 2019 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: API Changes to public APIs area: Networking area: Samples Samples Release Notes To be mentioned in the release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants