all-local:
../scripts/kernel-doc -html \
libwebsockets.c \
- parsers.c \
- ../test-server/test-server.c \
+ parsers.c \
+ libwebsockets.h \
> ../libwebsockets-api-doc.html
all-local:
../scripts/kernel-doc -html \
libwebsockets.c \
- parsers.c \
- ../test-server/test-server.c \
+ parsers.c \
+ libwebsockets.h \
> ../libwebsockets-api-doc.html
# Tell versions [3.59,3.63) of GNU make to not export all variables.
if (!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
!wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len) {
- if (wsi->callback)
- (wsi->callback)(wsi, LWS_CALLBACK_HTTP,
- &wsi->user_space[0],
+ if (wsi->protocol->callback)
+ (wsi->protocol->callback)(wsi, LWS_CALLBACK_HTTP,
+ &wsi->user_space,
wsi->utf8_token[WSI_TOKEN_GET_URI].token, 0);
wsi->state = WSI_STATE_HTTP;
return 0;
/* Make sure user side is happy about protocol */
- if (wsi->callback)
- wsi->callback(wsi, LWS_CALLBACK_PROTOCOL_FILTER,
- &wsi->user_space[0],
- wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
- 0);
+ while (wsi->protocol->callback) {
+
+ if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token == NULL) {
+ if (wsi->protocol->name == NULL)
+ break;
+ } else
+ if (strcmp(
+ wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
+ wsi->protocol->name) == 0)
+ break;
+
+ wsi->protocol++;
+ }
+ if (wsi->protocol->callback == NULL) {
+ if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token == NULL)
+ fprintf(stderr, "[no protocol] "
+ "not supported (use NULL .name)\n");
+ else
+ fprintf(stderr, "Requested protocol %s "
+ "not supported\n",
+ wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
+ goto bail;
+ }
+
+ /* allocate the per-connection user memory (if any) */
+
+ if (wsi->protocol->per_session_data_size) {
+ wsi->user_space = malloc(
+ wsi->protocol->per_session_data_size);
+ if (wsi->user_space == NULL) {
+ fprintf(stderr, "Out of memory for "
+ "conn user space\n");
+ goto bail;
+ }
+ }
/* create the response packet */
/* notify user code that we're ready to roll */
- if (wsi->callback)
- wsi->callback(wsi, LWS_CALLBACK_ESTABLISHED,
- &wsi->user_space[0], NULL, 0);
+ if (wsi->protocol->callback)
+ wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED,
+ &wsi->user_space, NULL, 0);
break;
case WSI_STATE_ESTABLISHED:
libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len);
+/* document the generic callback (it's a fake prototype under this) */
+/**
+ * callback() - User server actions
+ * @wsi: Opaque websocket instance pointer
+ * @reason: The reason for the call
+ * @user: Pointer to per-session user data allocated by library
+ * @in: Pointer used for some callback reasons
+ * @len: Length set for some callback reasons
+ *
+ * This callback is the way the user controls what is served. All the
+ * protocol detail is hidden and handled by the library.
+ *
+ * For each connection / session there is user data allocated that is
+ * pointed to by "user". You set the size of this user data area when
+ * the library is initialized with libwebsocket_create_server.
+ *
+ * You get an opportunity to initialize user data when called back with
+ * LWS_CALLBACK_ESTABLISHED reason.
+ *
+ * LWS_CALLBACK_ESTABLISHED: after successful websocket handshake
+ *
+ * LWS_CALLBACK_CLOSED: when the websocket session ends
+ *
+ * LWS_CALLBACK_SEND: opportunity to send to client (you would use
+ * libwebsocket_write() taking care about the
+ * special buffer requirements
+ * LWS_CALLBACK_RECEIVE: data has appeared for the server, it can be
+ * found at *in and is len bytes long
+ *
+ * LWS_CALLBACK_HTTP: an http request has come from a client that is not
+ * asking to upgrade the connection to a websocket
+ * one. This is a chance to serve http content,
+ * for example, to send a script to the client
+ * which will then open the websockets connection.
+ * @in points to the URI path requested and
+ * libwebsockets_serve_http_file() makes it very
+ * simple to send back a file to the client.
+ */
+extern int callback(struct libwebsocket * wsi,
+ enum libwebsocket_callback_reasons reason, void * user,
+ void *in, size_t len);
+
void
libwebsocket_close_and_free_session(struct libwebsocket *wsi)
wsi->state = WSI_STATE_DEAD_SOCKET;
- if (wsi->callback && n == WSI_STATE_ESTABLISHED)
- wsi->callback(wsi, LWS_CALLBACK_CLOSED, &wsi->user_space[0],
+ if (wsi->protocol->callback && n == WSI_STATE_ESTABLISHED)
+ wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, &wsi->user_space,
NULL, 0);
for (n = 0; n < WSI_TOKEN_COUNT; n++)
#ifdef LWS_OPENSSL_SUPPORT
}
#endif
+ if (wsi->user_space)
+ free(wsi->user_space);
+
free(wsi);
}
/**
* libwebsocket_create_server() - Create the listening websockets server
* @port: Port to listen on
- * @callback: The callback in user code to perform actual serving
- * @user_area_size: How much memory to allocate per connection session
- * which will be used by the user application to store
- * per-session data. A pointer to this space is given
- * when the user callback is called.
+ * @protocols: Array of structures listing supported protocols and a protocol-
+ * specific callback for each one. The list is ended with an
+ * entry that has a NULL callback pointer.
* @ssl_cert_filepath: If libwebsockets was compiled to use ssl, and you want
* to listen using SSL, set to the filepath to fetch the
* server cert from, otherwise NULL for unencrypted
*/
int libwebsocket_create_server(int port,
- int (*callback)(struct libwebsocket *,
- enum libwebsocket_callback_reasons,
- void *, void *, size_t),
- size_t user_area_size,
+ const struct libwebsocket_protocols *protocols,
const char * ssl_cert_filepath,
const char * ssl_private_key_filepath,
int gid, int uid)
/* SSL is happy and has a cert it's content with */
}
#endif
-
- if (!callback) {
- fprintf(stderr, "callback is not optional!\n");
- return -1;
- }
-
- /* sit there listening for connects, accept and spawn session servers */
-
+
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
fprintf(stderr, "ERROR opening socket");
if (n)
return sockfd;
- // drop any root privs for this thread
+ /* drop any root privs for this thread */
if (gid != -1)
if (setgid(gid))
if (setuid(uid))
fprintf(stderr, "setuid: %s\n", strerror(errno));
- /* we are running in a forked subprocess now */
-
+ /*
+ * sit there listening for connects, accept and service connections
+ * in a poll loop, without any further forking
+ */
+
listen(sockfd, 5);
fprintf(stderr, " Listening on port %d\n", port);
continue;
}
- wsi[fds_count] = malloc(sizeof(struct libwebsocket) +
- user_area_size);
+ wsi[fds_count] = malloc(sizeof(struct libwebsocket));
if (!wsi[fds_count])
return -1;
ntohs(cli_addr.sin_port), fd,
SSL_get_version(wsi[fds_count]->ssl));
- } else {
-// fprintf(stderr, "accepted new conn port %u on fd=%d\n",
-// ntohs(cli_addr.sin_port), fd);
- }
+ } else
#endif
+ debug("accepted new conn port %u on fd=%d\n",
+ ntohs(cli_addr.sin_port), fd);
/* intialize the instance struct */
wsi[fds_count]->utf8_token[n].token_len = 0;
}
- wsi[fds_count]->callback = callback;
+ /*
+ * these can only be set once the protocol is known
+ * we set an unestablished connection's protocol pointer
+ * to the start of the supported list, so it can look
+ * for matching ones during the handshake
+ */
+ wsi[fds_count]->protocol = protocols;
+ wsi[fds_count]->user_space = NULL;
+
/*
* Default protocol is 76
* After 76, there's a header specified to inform which
- * draft the client wants
+ * draft the client wants, when that's seen we modify
+ * the individual connection's spec revision accordingly
*/
wsi[fds_count]->ietf_spec_revision = 76;
if (!(fds[client].revents & POLLIN))
continue;
-
-// fprintf(stderr, "POLLIN\n");
#ifdef LWS_OPENSSL_SUPPORT
if (use_ssl)
#endif
n = recv(fds[client].fd, buf, sizeof(buf), 0);
-// fprintf(stderr, "read returned %d\n", n);
-
if (n < 0) {
fprintf(stderr, "Socket read returned %d\n", n);
continue;
if (libwebsocket_read(wsi[client], buf, n) >= 0)
continue;
- /* it closed and nuked wsi[client] */
+ /*
+ * it closed and nuked wsi[client], so remove the
+ * socket handle and wsi from our service list
+ */
nuke_this:
for (n = client; n < fds_count - 1; n++) {
fds[n] = fds[n + 1];
if (wsi[client]->state != WSI_STATE_ESTABLISHED)
continue;
-
- if (!wsi[client]->callback)
- continue;
- wsi[client]->callback(wsi[client], LWS_CALLBACK_SEND,
- &wsi[client]->user_space[0], NULL, 0);
+ wsi[client]->protocol->callback(wsi[client], LWS_CALLBACK_SEND,
+ &wsi[client]->user_space, NULL, 0);
}
continue;
LWS_CALLBACK_SEND,
LWS_CALLBACK_RECEIVE,
LWS_CALLBACK_HTTP,
- LWS_CALLBACK_PROTOCOL_FILTER,
};
enum libwebsocket_write_protocol {
struct libwebsocket;
+/**
+ * struct libwebsocket_protocols - List of protocols and handlers server
+ * supports.
+ * @name: Protocol name that must match the one given in the client
+ * Javascript new WebSocket(url, 'protocol') name
+ * @callback: The service callback used for this protocol. It allows the
+ * service action for an entire protocol to be encapsulated in
+ * the protocol-specific callback
+ * @per_session_data_size: Each new connection using this protocol gets
+ * this much memory allocated on connection establishment and
+ * freed on connection takedown. A pointer to this per-connection
+ * allocation is passed into the callback in the 'user' parameter
+ *
+ * This structure represents one protocol supported by the server. An
+ * array of these structures is passed to libwebsocket_create_server()
+ * allows as many protocols as you like to be handled by one server.
+ */
+
+struct libwebsocket_protocols {
+ const char *name;
+ int (*callback)(struct libwebsocket * wsi,
+ enum libwebsocket_callback_reasons reason, void * user,
+ void *in, size_t len);
+ size_t per_session_data_size;
+};
+
extern int libwebsocket_create_server(int port,
- int (*callback)(struct libwebsocket *wsi,
- enum libwebsocket_callback_reasons reason,
- void *user, void *in, size_t len),
- size_t user_space,
- const char * ssl_cert_filepath,
- const char * ssl_private_key_filepath,
- int gid, int uid);
+ const struct libwebsocket_protocols *protocols,
+ const char * ssl_cert_filepath,
+ const char * ssl_private_key_filepath, int gid, int uid);
/*
* IMPORTANT NOTICE!
if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER)
break;
issue:
- if (wsi->callback)
- wsi->callback(wsi, LWS_CALLBACK_RECEIVE,
- &wsi->user_space[0],
+ if (wsi->protocol->callback)
+ wsi->protocol->callback(wsi, LWS_CALLBACK_RECEIVE,
+ &wsi->user_space,
&wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING],
wsi->rx_user_buffer_head);
wsi->rx_user_buffer_head = 0;
*/
struct libwebsocket {
- int (*callback)(struct libwebsocket *,
- enum libwebsocket_callback_reasons reason, void *, void *, size_t);
+ const struct libwebsocket_protocols *protocol;
enum lws_connection_states state;
SSL *ssl;
#endif
- /* last */
- char user_space[0];
+ void *user_space;
};
extern void
+<h2>callback - User server actions</h2>
+<i>int</i>
+<b>callback</b>
+(<i>struct libwebsocket *</i> <b>wsi</b>,
+<i>enum libwebsocket_callback_reasons</i> <b>reason</b>,
+<i>void *</i> <b>user</b>,
+<i>void *</i> <b>in</b>,
+<i>size_t</i> <b>len</b>)
+<h3>Arguments</h3>
+<dl>
+<dt><b>wsi</b>
+<dd>Opaque websocket instance pointer
+<dt><b>reason</b>
+<dd>The reason for the call
+<dt><b>user</b>
+<dd>Pointer to per-session user data allocated by library
+<dt><b>in</b>
+<dd>Pointer used for some callback reasons
+<dt><b>len</b>
+<dd>Length set for some callback reasons
+</dl>
+<h3>Description</h3>
+<blockquote>
+This callback is the way the user controls what is served. All the
+protocol detail is hidden and handled by the library.
+<p>
+For each connection / session there is user data allocated that is
+pointed to by "user". You set the size of this user data area when
+the library is initialized with libwebsocket_create_server.
+<p>
+You get an opportunity to initialize user data when called back with
+LWS_CALLBACK_ESTABLISHED reason.
+</blockquote>
+<h3>LWS_CALLBACK_ESTABLISHED</h3>
+<blockquote>
+after successful websocket handshake
+</blockquote>
+<h3>LWS_CALLBACK_CLOSED</h3>
+<blockquote>
+when the websocket session ends
+</blockquote>
+<h3>LWS_CALLBACK_SEND</h3>
+<blockquote>
+opportunity to send to client (you would use
+<b>libwebsocket_write</b> taking care about the
+special buffer requirements
+</blockquote>
+<h3>LWS_CALLBACK_RECEIVE</h3>
+<blockquote>
+data has appeared for the server, it can be
+found at *in and is len bytes long
+</blockquote>
+<h3>LWS_CALLBACK_HTTP</h3>
+<blockquote>
+an http request has come from a client that is not
+asking to upgrade the connection to a websocket
+one. This is a chance to serve http content,
+for example, to send a script to the client
+which will then open the websockets connection.
+<tt><b>in</b></tt> points to the URI path requested and
+<b>libwebsockets_serve_http_file</b> makes it very
+simple to send back a file to the client.
+</blockquote>
+<hr>
<h2>libwebsocket_create_server - Create the listening websockets server</h2>
<i>int</i>
<b>libwebsocket_create_server</b>
(<i>int</i> <b>port</b>,
-<i>int (*</i><b>callback</b>) <i>(struct libwebsocket *, enum libwebsocket_callback_reasons, void *, void *, size_t)</i>,
-<i>size_t</i> <b>user_area_size</b>,
+<i>const struct libwebsocket_protocols *</i> <b>protocols</b>,
<i>const char *</i> <b>ssl_cert_filepath</b>,
<i>const char *</i> <b>ssl_private_key_filepath</b>,
<i>int</i> <b>gid</b>,
<dl>
<dt><b>port</b>
<dd>Port to listen on
-<dt><b>callback</b>
-<dd>The callback in user code to perform actual serving
-<dt><b>user_area_size</b>
-<dd>How much memory to allocate per connection session
-which will be used by the user application to store
-per-session data. A pointer to this space is given
-when the user callback is called.
+<dt><b>protocols</b>
+<dd>Array of structures listing supported protocols and a protocol-
+specific callback for each one. The list is ended with an
+entry that has a NULL callback pointer.
<dt><b>ssl_cert_filepath</b>
<dd>If libwebsockets was compiled to use ssl, and you want
to listen using SSL, set to the filepath to fetch the
local files down the http link in a single step.
</blockquote>
<hr>
+<h2>struct libwebsocket_protocols - List of protocols and handlers server supports.</h2>
+<b>struct libwebsocket_protocols</b> {<br>
+ <i>const char *</i> <b>name</b>;<br>
+ <i>int (*</i><b>callback</b>) <i>(struct libwebsocket * wsi,enum libwebsocket_callback_reasons reason, void * user,void *in, size_t len)</i>;<br>
+ <i>size_t</i> <b>per_session_data_size</b>;<br>
+};<br>
+<h3>Members</h3>
+<dl>
+<dt><b>name</b>
+<dd>Protocol name that must match the one given in the client
+Javascript new WebSocket(url, 'protocol') name
+<dt><b>callback</b>
+<dd>The service callback used for this protocol. It allows the
+service action for an entire protocol to be encapsulated in
+the protocol-specific callback
+<dt><b>per_session_data_size</b>
+<dd>Each new connection using this protocol gets
+this much memory allocated on connection establishment and
+freed on connection takedown. A pointer to this per-connection
+allocation is passed into the callback in the 'user' parameter
+</dl>
+<h3>Description</h3>
+<blockquote>
+This structure represents one protocol supported by the server. An
+array of these structures is passed to <b>libwebsocket_create_server</b>
+allows as many protocols as you like to be handled by one server.
+</blockquote>
+<hr>
static int port = 7681;
static int use_ssl = 0;
-struct per_session_data {
- int number;
-};
-
- /**
- * libwebsocket_callback() - User server actions
- * @wsi: Opaque websocket instance pointer
- * @reason: The reason for the call
- * @user: Pointer to per-session user data allocated by library
- * @in: Pointer used for some callback reasons
- * @len: Length set for some callback reasons
- *
- * This callback is the way the user controls what is served. All the
- * protocol detail is hidden and handled by the library.
- *
- * For each connection / session there is user data allocated that is
- * pointed to by "user". You set the size of this user data area when
- * the library is initialized with libwebsocket_create_server.
- *
- * You get an opportunity to initialize user data when called back with
- * LWS_CALLBACK_ESTABLISHED reason.
- *
- * LWS_CALLBACK_ESTABLISHED: after successful websocket handshake
- *
- * LWS_CALLBACK_CLOSED: when the websocket session ends
- *
- * LWS_CALLBACK_SEND: opportunity to send to client (you would use
- * libwebsocket_write() taking care about the
- * special buffer requirements
- * LWS_CALLBACK_RECEIVE: data has appeared for the server, it can be
- * found at *in and is len bytes long
- *
- * LWS_CALLBACK_HTTP: an http request has come from a client that is not
- * asking to upgrade the connection to a websocket
- * one. This is a chance to serve http content,
- * for example, to send a script to the client
- * which will then open the websockets connection.
- * @in points to the URI path requested and
- * libwebsockets_serve_http_file() makes it very
- * simple to send back a file to the client.
- *
- * LWS_CALLBACK_PROTOCOL_FILTER: before the confirmation handshake is sent
- * the user callback is given a chance to confirm
- * it's OK with the protocol that was requested
- * from the client. The protocol string (which
- * may be NULL if no protocol header was sent)
- * can be found at parameter @in. Return 0 from
- * the callback to allow the connection or nonzero
- * to abort the connection.
- */
+/* this protocol server (always the first one) just knows how to do HTTP */
-static int websocket_callback(struct libwebsocket * wsi,
+static int callback_http(struct libwebsocket * wsi,
enum libwebsocket_callback_reasons reason, void * user,
void *in, size_t len)
{
- int n;
- char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
- LWS_SEND_BUFFER_POST_PADDING];
- char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
- struct per_session_data * pss = user;
-
switch (reason) {
- /*
- * Websockets session handshake completed and is established
- */
- case LWS_CALLBACK_ESTABLISHED:
- fprintf(stderr, "Websocket connection established\n");
- pss->number = 0;
- break;
-
- /*
- * Websockets session is closed
- */
- case LWS_CALLBACK_CLOSED:
- fprintf(stderr, "Websocket connection closed\n");
- break;
-
- /*
- * Opportunity for us to send something on the connection
- */
- case LWS_CALLBACK_SEND:
- n = sprintf(p, "%d", pss->number++);
- n = libwebsocket_write(wsi, (unsigned char *)p, n,
- LWS_WRITE_TEXT);
- if (n < 0) {
- fprintf(stderr, "ERROR writing to socket");
- exit(1);
- }
- break;
- /*
- * Something has arrived for us on the connection, it's len bytes long
- * and is available at *in
- */
- case LWS_CALLBACK_RECEIVE:
- fprintf(stderr, "Received %d bytes payload\n", (int)len);
- if (strcmp(in, "reset\n") == 0)
- pss->number = 0;
- break;
-
- /*
- * The client has asked us for something in normal HTTP mode,
- * not websockets mode. Normally it means we want to send
- * our script / html to the client, and when that script runs
- * it will start up separate websocket connections.
- *
- * Interpret the URI string to figure out what is needed to send
- */
-
case LWS_CALLBACK_HTTP:
-
fprintf(stderr, "serving HTTP URI %s\n", in);
if (in && strcmp(in, "/favicon.ico") == 0) {
fprintf(stderr, "Failed to send HTTP file\n");
break;
- /*
- * This is our chance to choose if we support one of the requested
- * protocols or not. in points to the protocol string. Nonzero return
- * aborts the connection handshake
- */
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* dumb_increment protocol */
+
+struct per_session_data__dumb_increment {
+ int number;
+};
+
+static int
+callback_dumb_increment(struct libwebsocket * wsi,
+ enum libwebsocket_callback_reasons reason,
+ void * user, void *in, size_t len)
+{
+ int n;
+ char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
+ LWS_SEND_BUFFER_POST_PADDING];
+ unsigned char *p = (unsigned char *)&buf[LWS_SEND_BUFFER_PRE_PADDING];
+ struct per_session_data__dumb_increment * pss = user;
+
+ switch (reason) {
- case LWS_CALLBACK_PROTOCOL_FILTER:
- if (in == NULL) {
- fprintf(stderr, "Client did not request protocol\n");
- /* reject it */
+ case LWS_CALLBACK_ESTABLISHED:
+ pss->number = 0;
+ break;
+
+ case LWS_CALLBACK_SEND:
+ n = sprintf(p, "%d", pss->number++);
+ n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
+ if (n < 0) {
+ fprintf(stderr, "ERROR writing to socket");
return 1;
}
- fprintf(stderr, "Client requested protocol '%s'\n", in);
- if (strcmp(in, "dumb-increment-protocol") == 0)
- /* accept it */
- return 0;
+ break;
- /* reject the connection */
- return 1;
+ case LWS_CALLBACK_RECEIVE:
+ if (len < 6)
+ break;
+ if (strcmp(in, "reset\n") == 0)
+ pss->number = 0;
+ break;
+
+ default:
+ break;
}
return 0;
}
+
+/* list of supported protocols and callbacks */
+
+static const struct libwebsocket_protocols protocols[] = {
+ {
+ .name = "http-only",
+ .callback = callback_http,
+ .per_session_data_size = 0,
+ },
+ {
+ .name = "dumb-increment-protocol",
+ .callback = callback_dumb_increment,
+ .per_session_data_size =
+ sizeof(struct per_session_data__dumb_increment),
+ },
+ { /* end of list */
+ .callback = NULL
+ }
+};
+
static struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "port", required_argument, NULL, 'p' },
- { "protocol", required_argument, NULL, 'r' },
{ "ssl", no_argument, NULL, 's' },
{ NULL, 0, 0, 0 }
};
"licensed under LGPL2.1\n");
while (n >= 0) {
- n = getopt_long(argc, argv, "hp:r:", options, NULL);
+ n = getopt_long(argc, argv, "hp:", options, NULL);
if (n < 0)
continue;
switch (n) {
break;
case 'h':
fprintf(stderr, "Usage: test-server "
- "[--port=<p>] [--protocol=<v>]\n");
+ "[--port=<p>] [--ssl]\n");
exit(1);
}
}
if (!use_ssl)
cert_path = key_path = NULL;
- if (libwebsocket_create_server(port, websocket_callback,
- sizeof(struct per_session_data),
- cert_path, key_path, -1, -1) < 0) {
+ if (libwebsocket_create_server(port, protocols,
+ cert_path, key_path, -1, -1) < 0) {
fprintf(stderr, "libwebsocket init failed\n");
return -1;
}