{"action":"created","comment":{"url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments/51907940","id":51907940,"diff_hunk":"@@ -0,0 +1,507 @@\n+/*! \\file   janus_pfunix.c\n+ * \\author Lorenzo Miniero <lorenzo@meetecho.com>\n+ * \\copyright GNU General Public License v3\n+ * \\brief  Janus Unix Sockets transport plugin\n+ * \\details  This is an implementation of a Unix Sockets transport for the\n+ * Janus API. This means that, with the help of this module, local\n+ * applications can use Unix Sockets to make requests to the gateway.\n+ * This plugin makes use of the \\c SOCK_SEQPACKET socket type, so make\n+ * sure you're using the right one when writing a client application.\n+ * Pretty much as it happens with WebSockets, the same client socket can\n+ * be used for both sending requests and receiving notifications, without\n+ * any need for long polls. At the same time, without the concept of a\n+ * REST path, requests sent through the Unix Sockets interface will need\n+ * to include, when needed, additional pieces of information like\n+ * \\c session_id and \\c handle_id. That is, where you'd send a Janus\n+ * request related to a specific session to the \\c /janus/<session> path,\n+ * with Unix Sockets you'd have to send the same request with an additional\n+ * \\c session_id field in the JSON payload. The same applies for the handle.\n+ * \\note When you create a session using Unix Sockets, a subscription to\n+ * the events related to it is done automatically, so no need for an\n+ * explicit request as the GET in the plain HTTP API. Closing a client\n+ * Unix Socket will also destroy all the sessions it created.\n+ *\n+ * \\ingroup transports\n+ * \\ref transports\n+ */\n+\n+#include \"transport.h\"\n+\n+#include <poll.h>\n+#include <sys/un.h>\n+\n+#include \"../debug.h\"\n+#include \"../apierror.h\"\n+#include \"../config.h\"\n+#include \"../mutex.h\"\n+#include \"../utils.h\"\n+\n+\n+/* Transport plugin information */\n+#define JANUS_PFUNIX_VERSION\t\t\t1\n+#define JANUS_PFUNIX_VERSION_STRING\t\t\"0.0.1\"\n+#define JANUS_PFUNIX_DESCRIPTION\t\t\"This transport plugin adds Unix Sockets support to the Janus API.\"\n+#define JANUS_PFUNIX_NAME\t\t\t\t\"JANUS Unix Sockets transport plugin\"\n+#define JANUS_PFUNIX_AUTHOR\t\t\t\t\"Meetecho s.r.l.\"\n+#define JANUS_PFUNIX_PACKAGE\t\t\t\"janus.transport.pfunix\"\n+\n+/* Transport methods */\n+janus_transport *create(void);\n+int janus_pfunix_init(janus_transport_callbacks *callback, const char *config_path);\n+void janus_pfunix_destroy(void);\n+int janus_pfunix_get_api_compatibility(void);\n+int janus_pfunix_get_version(void);\n+const char *janus_pfunix_get_version_string(void);\n+const char *janus_pfunix_get_description(void);\n+const char *janus_pfunix_get_name(void);\n+const char *janus_pfunix_get_author(void);\n+const char *janus_pfunix_get_package(void);\n+gboolean janus_pfunix_is_janus_api_enabled(void);\n+gboolean janus_pfunix_is_admin_api_enabled(void);\n+int janus_pfunix_send_message(void *transport, void *request_id, gboolean admin, json_t *message);\n+void janus_pfunix_session_created(void *transport, guint64 session_id);\n+void janus_pfunix_session_over(void *transport, guint64 session_id, gboolean timeout);\n+\n+\n+/* Transport setup */\n+static janus_transport janus_pfunix_transport =\n+\tJANUS_TRANSPORT_INIT (\n+\t\t.init = janus_pfunix_init,\n+\t\t.destroy = janus_pfunix_destroy,\n+\n+\t\t.get_api_compatibility = janus_pfunix_get_api_compatibility,\n+\t\t.get_version = janus_pfunix_get_version,\n+\t\t.get_version_string = janus_pfunix_get_version_string,\n+\t\t.get_description = janus_pfunix_get_description,\n+\t\t.get_name = janus_pfunix_get_name,\n+\t\t.get_author = janus_pfunix_get_author,\n+\t\t.get_package = janus_pfunix_get_package,\n+\n+\t\t.is_janus_api_enabled = janus_pfunix_is_janus_api_enabled,\n+\t\t.is_admin_api_enabled = janus_pfunix_is_admin_api_enabled,\n+\n+\t\t.send_message = janus_pfunix_send_message,\n+\t\t.session_created = janus_pfunix_session_created,\n+\t\t.session_over = janus_pfunix_session_over,\n+\t);\n+\n+/* Transport creator */\n+janus_transport *create(void) {\n+\tJANUS_LOG(LOG_VERB, \"%s created!\\n\", JANUS_PFUNIX_NAME);\n+\treturn &janus_pfunix_transport;\n+}\n+\n+\n+/* Useful stuff */\n+static gint initialized = 0, stopping = 0;\n+static janus_transport_callbacks *gateway = NULL;\n+\n+#define BUFFER_SIZE\t\t8192\n+\n+struct sockaddr_un sizecheck;\n+#ifndef UNIX_PATH_MAX\n+#define UNIX_PATH_MAX sizeof(sizecheck.sun_path)\n+#endif\n+\n+/* Unix Sockets server thread */\n+static GThread *pfunix_thread = NULL;\n+void *janus_pfunix_thread(void *data);\n+\n+/* Unix Sockets servers */\n+static int pfd = -1, admin_pfd = -1;\n+/* Socket pair to notify about the need for outgoing data */\n+static int write_fd[2];\n+\n+/* Unix Sockets client session */\n+typedef struct janus_pfunix_client {\n+\tint fd;\t\t\t\t\t\t/* Client socket */\n+\tgboolean admin;\t\t\t\t/* Whether this client is for the Admin or Janus API */\n+\tGAsyncQueue *messages;\t\t/* Queue of outgoing messages to push */\n+\tgboolean session_timeout;\t/* Whether a Janus session timeout occurred in the core */\n+} janus_pfunix_client;\n+static GHashTable *clients = NULL, *clients_by_fd = NULL;\n+static janus_mutex clients_mutex;\n+\n+\n+/* Helper to create a named Unix Socket out of the path to link to */\n+static int janus_pfunix_create_socket(char *pfname) {\n+\tif(pfname == NULL)\n+\t\treturn -1;\n+\tint fd = -1;\n+\tif(strlen(pfname) > UNIX_PATH_MAX) {\n+\t\tJANUS_LOG(LOG_WARN, \"The provided path name (%s) is longer than %lu characters, it will be truncated\\n\", pfname, UNIX_PATH_MAX);\n+\t\tpfname[UNIX_PATH_MAX] = '\\0';\n+\t}\n+\t/* Create socket */\n+\tfd = socket(PF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK, 0);\n+\tif(fd < 0) {\n+\t\tJANUS_LOG(LOG_FATAL, \"Unix Sockets %s creation failed: %d, %s\\n\", pfname, errno, strerror(errno));\n+\t} else {\n+\t\t/* Unlink before binding */\n+\t\tunlink(pfname);\n+\t\t/* Let's bind to the provided path now */\n+\t\tstruct sockaddr_un address;\n+\t\tmemset(&address, 0, sizeof(address));\n+\t\taddress.sun_family = AF_UNIX;\n+\t\tg_snprintf(address.sun_path, UNIX_PATH_MAX, \"%s\", pfname);\n+\t\tJANUS_LOG(LOG_VERB, \"Binding Unix Socket %s... (Janus API)\\n\", pfname);\n+\t\tif(bind(fd, (struct sockaddr *)&address, sizeof(address)) != 0) {\n+\t\t\tJANUS_LOG(LOG_FATAL, \"Bind for Unix Socket %s failed: %d, %s\\n\", pfname, errno, strerror(errno));\n+\t\t\tclose(fd);\n+\t\t\tfd = -1;\n+\t\t} else {\n+\t\t\tJANUS_LOG(LOG_VERB, \"Listening on Unix Socket %s...\\n\", pfname);\n+\t\t\tif(listen(fd, 128) != 0) {\n+\t\t\t\tJANUS_LOG(LOG_FATAL, \"Listening on Unix Socket %s failed: %d, %s\\n\", pfname, errno, strerror(errno));\n+\t\t\t\tclose(fd);\n+\t\t\t\tfd = -1;\n+\t\t\t}\n+\t\t}\n+\t}\n+\treturn fd;\n+}\n+\n+/* Transport implementation */\n+int janus_pfunix_init(janus_transport_callbacks *callback, const char *config_path) {\n+\tif(g_atomic_int_get(&stopping)) {\n+\t\t/* Still stopping from before */\n+\t\treturn -1;\n+\t}\n+\tif(callback == NULL || config_path == NULL) {\n+\t\t/* Invalid arguments */\n+\t\treturn -1;\n+\t}\n+\n+\t/* This is the callback we'll need to invoke to contact the gateway */\n+\tgateway = callback;\n+\n+\t/* Read configuration */\n+\tchar filename[255];\n+\tg_snprintf(filename, 255, \"%s/%s.cfg\", config_path, JANUS_PFUNIX_PACKAGE);\n+\tJANUS_LOG(LOG_VERB, \"Configuration file: %s\\n\", filename);\n+\tjanus_config *config = janus_config_parse(filename);\n+\tif(config != NULL) {\n+\t\t/* Handle configuration */\n+\t\tjanus_config_print(config);\n+\n+\t\t/* First of all Initialize the socketpair for writeable notifications */\n+\t\tif(socketpair(PF_LOCAL, SOCK_STREAM, 0, write_fd) < 0) {\n+\t\t\tJANUS_LOG(LOG_FATAL, \"Error creating socket pair for writeable events: %d, %s\\n\", errno, strerror(errno));\n+\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Setup the Janus API Unix Sockets server(s) */\n+\t\tjanus_config_item *item = janus_config_get_item_drilldown(config, \"general\", \"enabled\");\n+\t\tif(!item || !item->value || !janus_is_true(item->value)) {\n+\t\t\tJANUS_LOG(LOG_WARN, \"Unix Sockets server disabled (Janus API)\\n\");\n+\t\t} else {\n+\t\t\titem = janus_config_get_item_drilldown(config, \"general\", \"path\");\n+\t\t\tchar *pfname = (char *)(item && item->value ? item->value : \"/tmp/ux-janusapi\");\n+\t\t\tpfd = janus_pfunix_create_socket(pfname);\n+\t\t}\n+\t\t/* Do the same for the Admin API, if enabled */\n+\t\titem = janus_config_get_item_drilldown(config, \"admin\", \"admin_enabled\");\n+\t\tif(!item || !item->value || !janus_is_true(item->value)) {\n+\t\t\tJANUS_LOG(LOG_WARN, \"Unix Sockets server disabled (Admin API)\\n\");\n+\t\t} else {\n+\t\t\titem = janus_config_get_item_drilldown(config, \"admin\", \"admin_path\");\n+\t\t\tchar *pfname = (char *)(item && item->value ? item->value : \"/tmp/ux-janusadmin\");\n+\t\t\tadmin_pfd = janus_pfunix_create_socket(pfname);\n+\t\t}\n+\t}\n+\tjanus_config_destroy(config);\n+\tconfig = NULL;\n+\tif(pfd < 0 && admin_pfd < 0) {\n+\t\tJANUS_LOG(LOG_FATAL, \"No Unix Sockets server started, giving up...\\n\");\n+\t\treturn -1;\t/* No point in keeping the plugin loaded */\n+\t}\n+\n+\t/* Create a couple of hashtables for all clients */\n+\tclients = g_hash_table_new(NULL, NULL);\n+\tclients_by_fd = g_hash_table_new(NULL, NULL);\n+\tjanus_mutex_init(&clients_mutex);\n+\n+\t/* Start the Unix Sockets service thread */\n+\tGError *error = NULL;\n+\tpfunix_thread = g_thread_try_new(\"pfunix thread\", &janus_pfunix_thread, NULL, &error);\n+\tif(!pfunix_thread) {\n+\t\tg_atomic_int_set(&initialized, 0);\n+\t\tJANUS_LOG(LOG_ERR, \"Got error %d (%s) trying to launch the Unix Sockets thread...\\n\", error->code, error->message ? error->message : \"??\");\n+\t\treturn -1;\n+\t}\n+\n+\t/* Done */\n+\tg_atomic_int_set(&initialized, 1);\n+\tJANUS_LOG(LOG_INFO, \"%s initialized!\\n\", JANUS_PFUNIX_NAME);\n+\treturn 0;\n+}\n+\n+void janus_pfunix_destroy(void) {\n+\tif(!g_atomic_int_get(&initialized))\n+\t\treturn;\n+\tg_atomic_int_set(&stopping, 1);\n+\n+\t/* Stop the service thread */\n+\tint res = 0;\n+\tdo {\n+\t\tres = write(write_fd[1], \"x\", 1);\n+\t} while(res == -1 && errno == EINTR);\n+\n+\tif(pfunix_thread != NULL) {\n+\t\tg_thread_join(pfunix_thread);\n+\t\tpfunix_thread = NULL;\n+\t}\n+\n+\tg_atomic_int_set(&initialized, 0);\n+\tg_atomic_int_set(&stopping, 0);\n+\tJANUS_LOG(LOG_INFO, \"%s destroyed!\\n\", JANUS_PFUNIX_NAME);\n+}\n+\n+int janus_pfunix_get_api_compatibility(void) {\n+\t/* Important! This is what your plugin MUST always return: don't lie here or bad things will happen */\n+\treturn JANUS_TRANSPORT_API_VERSION;\n+}\n+\n+int janus_pfunix_get_version(void) {\n+\treturn JANUS_PFUNIX_VERSION;\n+}\n+\n+const char *janus_pfunix_get_version_string(void) {\n+\treturn JANUS_PFUNIX_VERSION_STRING;\n+}\n+\n+const char *janus_pfunix_get_description(void) {\n+\treturn JANUS_PFUNIX_DESCRIPTION;\n+}\n+\n+const char *janus_pfunix_get_name(void) {\n+\treturn JANUS_PFUNIX_NAME;\n+}\n+\n+const char *janus_pfunix_get_author(void) {\n+\treturn JANUS_PFUNIX_AUTHOR;\n+}\n+\n+const char *janus_pfunix_get_package(void) {\n+\treturn JANUS_PFUNIX_PACKAGE;\n+}\n+\n+gboolean janus_pfunix_is_janus_api_enabled(void) {\n+\treturn pfd > -1;\n+}\n+\n+gboolean janus_pfunix_is_admin_api_enabled(void) {\n+\treturn admin_pfd > -1;\n+}\n+\n+int janus_pfunix_send_message(void *transport, void *request_id, gboolean admin, json_t *message) {\n+\tif(message == NULL)\n+\t\treturn -1;\n+\tif(transport == NULL) {\n+\t\tg_free(message);\n+\t\treturn -1;\n+\t}\n+\t/* Make sure this is related to a still valid Unix Sockets session */\n+\tjanus_pfunix_client *client = (janus_pfunix_client *)transport;\n+\tjanus_mutex_lock(&clients_mutex);\n+\tif(g_hash_table_lookup(clients, client) == NULL || client->fd < 0) {\n+\t\tjanus_mutex_unlock(&clients_mutex);\n+\t\tJANUS_LOG(LOG_WARN, \"Outgoing message for invalid client %p\\n\", client);\n+\t\tg_free(message);\n+\t\tmessage = NULL;\n+\t\treturn -1;\n+\t}\n+\tjanus_mutex_unlock(&clients_mutex);\n+\t/* Convert to string and enqueue */\n+\tchar *payload = json_dumps(message, JSON_INDENT(3) | JSON_PRESERVE_ORDER);\n+\tjson_decref(message);\n+\tg_async_queue_push(client->messages, payload);\n+\t/* Notify the thread there's data to send */\n+\tint res = 0;\n+\tdo {\n+\t\tres = write(write_fd[1], \"x\", 1);\n+\t} while(res == -1 && errno == EINTR);\n+\treturn 0;\n+}\n+\n+void janus_pfunix_session_created(void *transport, guint64 session_id) {\n+\t/* We don't care */\n+}\n+\n+void janus_pfunix_session_over(void *transport, guint64 session_id, gboolean timeout) {\n+\t/* We only care if it's a timeout: if so, close the connection */\n+\tif(transport == NULL || !timeout)\n+\t\treturn;\n+\t/* FIXME Should we really close the connection in case of a timeout? */\n+\tjanus_pfunix_client *client = (janus_pfunix_client *)transport;\n+\tjanus_mutex_lock(&clients_mutex);\n+\tif(g_hash_table_lookup(clients, client) != NULL && client->fd > -1) {\n+\t\t/* Shutdown the client socket */\n+\t\tclient->session_timeout = TRUE;\n+\t\tshutdown(client->fd, SHUT_WR);\n+\t}\n+\tjanus_mutex_unlock(&clients_mutex);\n+}\n+\n+\n+/* Thread */\n+void *janus_pfunix_thread(void *data) {\n+\tJANUS_LOG(LOG_INFO, \"Unix Sockets thread started\\n\");\n+\n+\tint fds = 0;\n+\tstruct pollfd poll_fds[1024];\t/* FIXME Should we allow for more clients? */\n+\tchar buffer[BUFFER_SIZE];","path":"transports/janus_pfunix.c","position":353,"original_position":353,"commit_id":"0fba868208b9549dd89997e63fbbe3b7e9e7fbd0","original_commit_id":"0fba868208b9549dd89997e63fbbe3b7e9e7fbd0","user":{"login":"jayridge","id":107435,"avatar_url":"https://avatars.githubusercontent.com/u/107435?v=3","gravatar_id":"","url":"https://api.github.com/users/jayridge","html_url":"https://github.com/jayridge","followers_url":"https://api.github.com/users/jayridge/followers","following_url":"https://api.github.com/users/jayridge/following{/other_user}","gists_url":"https://api.github.com/users/jayridge/gists{/gist_id}","starred_url":"https://api.github.com/users/jayridge/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jayridge/subscriptions","organizations_url":"https://api.github.com/users/jayridge/orgs","repos_url":"https://api.github.com/users/jayridge/repos","events_url":"https://api.github.com/users/jayridge/events{/privacy}","received_events_url":"https://api.github.com/users/jayridge/received_events","type":"User","site_admin":false},"body":"Might buffer size be a candidate for config? The difficulty with SOCK_SEQPACKET here is that there is no fixed max request size.","created_at":"2016-02-04T17:38:58Z","updated_at":"2016-02-04T17:38:58Z","html_url":"https://github.com/meetecho/janus-gateway/pull/458#discussion_r51907940","pull_request_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458","_links":{"self":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments/51907940"},"html":{"href":"https://github.com/meetecho/janus-gateway/pull/458#discussion_r51907940"},"pull_request":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458"}}},"pull_request":{"url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458","id":58164224,"html_url":"https://github.com/meetecho/janus-gateway/pull/458","diff_url":"https://github.com/meetecho/janus-gateway/pull/458.diff","patch_url":"https://github.com/meetecho/janus-gateway/pull/458.patch","issue_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/458","number":458,"state":"open","locked":false,"title":"New transport module (Unix Sockets)","user":{"login":"lminiero","id":3684796,"avatar_url":"https://avatars.githubusercontent.com/u/3684796?v=3","gravatar_id":"","url":"https://api.github.com/users/lminiero","html_url":"https://github.com/lminiero","followers_url":"https://api.github.com/users/lminiero/followers","following_url":"https://api.github.com/users/lminiero/following{/other_user}","gists_url":"https://api.github.com/users/lminiero/gists{/gist_id}","starred_url":"https://api.github.com/users/lminiero/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/lminiero/subscriptions","organizations_url":"https://api.github.com/users/lminiero/orgs","repos_url":"https://api.github.com/users/lminiero/repos","events_url":"https://api.github.com/users/lminiero/events{/privacy}","received_events_url":"https://api.github.com/users/lminiero/received_events","type":"User","site_admin":false},"body":"This patch adds a new transport module, specifically for controlling a Janus instance via Unix Sockets as an alternative to the already available HTTP, WebSockets and RabbitMQ transports. This would be particularly useful in contexts where Janus and its controlling application are colocated, and where a dedicated control channel would be more efficient. Credits for the suggestion about the new module and hints go to @saghul!\r\n\r\nAs of now, you can configure two named server sockets, one for the Janus API and one for the Admin API. When writing a client, you need to specify the ```SOCK_SEQPACKET``` type, as that's what both interfaces make use of. Changing this to something different (e.g., ```SOCK_DGRAM```) would be trivial and I guess could be added to the configuration as well, so if there's interest in that we can do that. There's no plan on adding support for ```SOCK_STREAM```, instead, as we need the automatic message framing that a stream does not provide.\r\n\r\nI haven't tested this extensively (just did a few validations), so feedback and bug reports are more than welcome!","created_at":"2016-02-03T16:41:35Z","updated_at":"2016-02-04T17:38:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4544c72d3e1d93ceb18a9c7080f10c975409a979","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458/commits","review_comments_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458/comments","review_comment_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments{/number}","comments_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/458/comments","statuses_url":"https://api.github.com/repos/meetecho/janus-gateway/statuses/0fba868208b9549dd89997e63fbbe3b7e9e7fbd0","head":{"label":"meetecho:pf-unix","ref":"pf-unix","sha":"0fba868208b9549dd89997e63fbbe3b7e9e7fbd0","user":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"repo":{"id":16734696,"name":"janus-gateway","full_name":"meetecho/janus-gateway","owner":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/meetecho/janus-gateway","description":"Janus WebRTC Gateway","fork":false,"url":"https://api.github.com/repos/meetecho/janus-gateway","forks_url":"https://api.github.com/repos/meetecho/janus-gateway/forks","keys_url":"https://api.github.com/repos/meetecho/janus-gateway/keys{/key_id}","collaborators_url":"https://api.github.com/repos/meetecho/janus-gateway/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/meetecho/janus-gateway/teams","hooks_url":"https://api.github.com/repos/meetecho/janus-gateway/hooks","issue_events_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/events{/number}","events_url":"https://api.github.com/repos/meetecho/janus-gateway/events","assignees_url":"https://api.github.com/repos/meetecho/janus-gateway/assignees{/user}","branches_url":"https://api.github.com/repos/meetecho/janus-gateway/branches{/branch}","tags_url":"https://api.github.com/repos/meetecho/janus-gateway/tags","blobs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/meetecho/janus-gateway/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/refs{/sha}","trees_url":"https://api.github.com/repos/meetecho/janus-gateway/git/trees{/sha}","statuses_url":"https://api.github.com/repos/meetecho/janus-gateway/statuses/{sha}","languages_url":"https://api.github.com/repos/meetecho/janus-gateway/languages","stargazers_url":"https://api.github.com/repos/meetecho/janus-gateway/stargazers","contributors_url":"https://api.github.com/repos/meetecho/janus-gateway/contributors","subscribers_url":"https://api.github.com/repos/meetecho/janus-gateway/subscribers","subscription_url":"https://api.github.com/repos/meetecho/janus-gateway/subscription","commits_url":"https://api.github.com/repos/meetecho/janus-gateway/commits{/sha}","git_commits_url":"https://api.github.com/repos/meetecho/janus-gateway/git/commits{/sha}","comments_url":"https://api.github.com/repos/meetecho/janus-gateway/comments{/number}","issue_comment_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/comments{/number}","contents_url":"https://api.github.com/repos/meetecho/janus-gateway/contents/{+path}","compare_url":"https://api.github.com/repos/meetecho/janus-gateway/compare/{base}...{head}","merges_url":"https://api.github.com/repos/meetecho/janus-gateway/merges","archive_url":"https://api.github.com/repos/meetecho/janus-gateway/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/meetecho/janus-gateway/downloads","issues_url":"https://api.github.com/repos/meetecho/janus-gateway/issues{/number}","pulls_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls{/number}","milestones_url":"https://api.github.com/repos/meetecho/janus-gateway/milestones{/number}","notifications_url":"https://api.github.com/repos/meetecho/janus-gateway/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/meetecho/janus-gateway/labels{/name}","releases_url":"https://api.github.com/repos/meetecho/janus-gateway/releases{/id}","deployments_url":"https://api.github.com/repos/meetecho/janus-gateway/deployments","created_at":"2014-02-11T15:14:39Z","updated_at":"2016-02-03T08:53:34Z","pushed_at":"2016-02-04T16:51:50Z","git_url":"git://github.com/meetecho/janus-gateway.git","ssh_url":"git@github.com:meetecho/janus-gateway.git","clone_url":"https://github.com/meetecho/janus-gateway.git","svn_url":"https://github.com/meetecho/janus-gateway","homepage":null,"size":12234,"stargazers_count":462,"watchers_count":462,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":198,"mirror_url":null,"open_issues_count":20,"forks":198,"open_issues":20,"watchers":462,"default_branch":"master"}},"base":{"label":"meetecho:master","ref":"master","sha":"03492d9fb03212cfd35fcfeac749ffe50cd8d22e","user":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"repo":{"id":16734696,"name":"janus-gateway","full_name":"meetecho/janus-gateway","owner":{"login":"meetecho","id":4520545,"avatar_url":"https://avatars.githubusercontent.com/u/4520545?v=3","gravatar_id":"","url":"https://api.github.com/users/meetecho","html_url":"https://github.com/meetecho","followers_url":"https://api.github.com/users/meetecho/followers","following_url":"https://api.github.com/users/meetecho/following{/other_user}","gists_url":"https://api.github.com/users/meetecho/gists{/gist_id}","starred_url":"https://api.github.com/users/meetecho/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/meetecho/subscriptions","organizations_url":"https://api.github.com/users/meetecho/orgs","repos_url":"https://api.github.com/users/meetecho/repos","events_url":"https://api.github.com/users/meetecho/events{/privacy}","received_events_url":"https://api.github.com/users/meetecho/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/meetecho/janus-gateway","description":"Janus WebRTC Gateway","fork":false,"url":"https://api.github.com/repos/meetecho/janus-gateway","forks_url":"https://api.github.com/repos/meetecho/janus-gateway/forks","keys_url":"https://api.github.com/repos/meetecho/janus-gateway/keys{/key_id}","collaborators_url":"https://api.github.com/repos/meetecho/janus-gateway/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/meetecho/janus-gateway/teams","hooks_url":"https://api.github.com/repos/meetecho/janus-gateway/hooks","issue_events_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/events{/number}","events_url":"https://api.github.com/repos/meetecho/janus-gateway/events","assignees_url":"https://api.github.com/repos/meetecho/janus-gateway/assignees{/user}","branches_url":"https://api.github.com/repos/meetecho/janus-gateway/branches{/branch}","tags_url":"https://api.github.com/repos/meetecho/janus-gateway/tags","blobs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/meetecho/janus-gateway/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/meetecho/janus-gateway/git/refs{/sha}","trees_url":"https://api.github.com/repos/meetecho/janus-gateway/git/trees{/sha}","statuses_url":"https://api.github.com/repos/meetecho/janus-gateway/statuses/{sha}","languages_url":"https://api.github.com/repos/meetecho/janus-gateway/languages","stargazers_url":"https://api.github.com/repos/meetecho/janus-gateway/stargazers","contributors_url":"https://api.github.com/repos/meetecho/janus-gateway/contributors","subscribers_url":"https://api.github.com/repos/meetecho/janus-gateway/subscribers","subscription_url":"https://api.github.com/repos/meetecho/janus-gateway/subscription","commits_url":"https://api.github.com/repos/meetecho/janus-gateway/commits{/sha}","git_commits_url":"https://api.github.com/repos/meetecho/janus-gateway/git/commits{/sha}","comments_url":"https://api.github.com/repos/meetecho/janus-gateway/comments{/number}","issue_comment_url":"https://api.github.com/repos/meetecho/janus-gateway/issues/comments{/number}","contents_url":"https://api.github.com/repos/meetecho/janus-gateway/contents/{+path}","compare_url":"https://api.github.com/repos/meetecho/janus-gateway/compare/{base}...{head}","merges_url":"https://api.github.com/repos/meetecho/janus-gateway/merges","archive_url":"https://api.github.com/repos/meetecho/janus-gateway/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/meetecho/janus-gateway/downloads","issues_url":"https://api.github.com/repos/meetecho/janus-gateway/issues{/number}","pulls_url":"https://api.github.com/repos/meetecho/janus-gateway/pulls{/number}","milestones_url":"https://api.github.com/repos/meetecho/janus-gateway/milestones{/number}","notifications_url":"https://api.github.com/repos/meetecho/janus-gateway/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/meetecho/janus-gateway/labels{/name}","releases_url":"https://api.github.com/repos/meetecho/janus-gateway/releases{/id}","deployments_url":"https://api.github.com/repos/meetecho/janus-gateway/deployments","created_at":"2014-02-11T15:14:39Z","updated_at":"2016-02-03T08:53:34Z","pushed_at":"2016-02-04T16:51:50Z","git_url":"git://github.com/meetecho/janus-gateway.git","ssh_url":"git@github.com:meetecho/janus-gateway.git","clone_url":"https://github.com/meetecho/janus-gateway.git","svn_url":"https://github.com/meetecho/janus-gateway","homepage":null,"size":12234,"stargazers_count":462,"watchers_count":462,"language":"C","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":198,"mirror_url":null,"open_issues_count":20,"forks":198,"open_issues":20,"watchers":462,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458"},"html":{"href":"https://github.com/meetecho/janus-gateway/pull/458"},"issue":{"href":"https://api.github.com/repos/meetecho/janus-gateway/issues/458"},"comments":{"href":"https://api.github.com/repos/meetecho/janus-gateway/issues/458/comments"},"review_comments":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458/comments"},"review_comment":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/meetecho/janus-gateway/pulls/458/commits"},"statuses":{"href":"https://api.github.com/repos/meetecho/janus-gateway/statuses/0fba868208b9549dd89997e63fbbe3b7e9e7fbd0"}}}}