{"data":{"logo":{"childImageSharp":{"fixed":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAABYlAAAWJQFJUiTwAAABHklEQVQY00WRvyvGYRTFP6/3tVtkMxmUyWqSP8BmkKQMkvRmNbxJSsmk8xVlU1IU8qMMfhSlDAYDyiSTRUzGe/R8v48Mt+ee7j3nnnsfLPoslix+LD4sVkJ0W+CCugtqZS7aXJRRTzj+cFXrsuhPeQLjIV4triyKELdWRXIBUZRvLTIuQzn+8aHFYsrJytsWNxZrFmMWwyFaFlsWg5k0k/vmLBohmhZnFkOJG2LBYjk1tlvsh7DFXiZfW1xY7FhsWoxmsXWLyzz0IMRUVLXvEE8WA4ncsHi3GLG4CzFpcW/Ra7FhsZrP0ZMHzFocu6BZnkTMW7xZnIaYJn/Kl0VndpbWfrTosDixeLB4Ts5CfFqcWxxZvCTXUYntWkwkQ7/BJu9d4GccFQAAAABJRU5ErkJggg==","width":400,"height":124,"src":"/static/e85f9e27853b1ae2b55ad25e0aa99c71/140ea/knack-logo-orange.png","srcSet":"/static/e85f9e27853b1ae2b55ad25e0aa99c71/140ea/knack-logo-orange.png 1x,\n/static/e85f9e27853b1ae2b55ad25e0aa99c71/26d9e/knack-logo-orange.png 1.5x,\n/static/e85f9e27853b1ae2b55ad25e0aa99c71/a3fa0/knack-logo-orange.png 2x,\n/static/e85f9e27853b1ae2b55ad25e0aa99c71/4cc84/knack-logo-orange.png 3x"}}},"markdownRemark":{"html":"<p>WebSockets are a ubiquitous tool for building real-time experiences for users. They provide a persistent, bidirectional connection between a client and a server. For example, say the frontend of your app needs to stay up-to-date within seconds of your backend to do things like deliver notifications, status updates, etc. You may have implemented this as a periodic check, or poll, of the backend via some API like REST or GraphQL. Now that you have more users, and/or your processes have become more complex, these requests can start to weigh on your infrastructure capacity. That’s a problem that WebSockets can help solve.</p>\n<p>Laravel and its ecosystem play a significant role in our stack at Knack, and recently we embarked on a new WebSocket server implementation which would work closely with our Laravel-based core API. For the uninitiated, <a href=\"https://laravel.com/\">Laravel</a> is a popular PHP framework and ecosystem that provides a rich set of tools and features for building modern web applications. We decided to use the <a href=\"https://github.com/swooletw/laravel-swoole\">swooletw/laravel-swoole</a> library to build our server-side implementation of a custom subprotocol and I had the opportunity to document some of my experiences along the way. I’ll show how to get started with the library, provide some background information, some caveats to consider, and drop a few tips. Let’s get started!</p>\n<h2>WebSocket Server Solutions for Laravel</h2>\n<p>First, we are working with the assumption that you want, or need, to host your own WebSocket server, and that you want to do so in PHP. With a stateful protocol like WebSockets we need to be able to run PHP concurrently. There are basically two different approaches: language-level and extension-level.</p>\n<p>Language-level libraries and frameworks implement concurrency in pure PHP. Examples include <a href=\"https://amphp.org/\">AMPHP</a> and <a href=\"https://reactphp.org/\">ReactPHP</a>. In contrast, extensions are written in a lower-level language, typically C/C++, and expose APIs at the PHP-level to access the extension-level functionality. <a href=\"https://www.swoole.com/\">Swoole</a> and <a href=\"https://openswoole.com/\">OpenSwoole</a> are examples of PHP extensions. In short, Swoole is an event-driven, asynchronous, multithreaded framework that makes it easy to build performant, concurrent servers.</p>\n<p>Now let’s evaluate pre-packaged WebSocket Server solutions that easily plug into a Laravel app. On the language-level side we have the seemingly most popular choice: the <a href=\"https://beyondco.de/docs/laravel-websockets\">beyondcode/laravel-websockets</a>, a package by Beyond Code. It is built on <a href=\"http://socketo.me/\">Ratchet</a> (which is built on <a href=\"https://reactphp.org/\">ReactPHP</a>), has a Pusher protocol implementation, and plugs well into the Laravel framework’s broadcasting API. While this supports a lot of use cases and does so in a familiar “Laravel way” it is not very flexible when you need something like a custom subprotocol implementation,  and it is inherently limited in performance due to being built on a PHP server core rather than a C/C++ one.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/f1c1ec6871ccc541e473d90056b04233/6b6b8/stargazer_statistics.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 63.53135313531353%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1ElEQVQ4y52TW4/aMBCF8///Fw9FKttSxIYC2rJZck+cC7nZSb7KhgTasi8daWJPPD4+czy2AIo8IwxCsiwjTVMzCiGIosjEwzDoNMZxvLuOp39KMpYZQ5ljdVLyY71m+fU7X5ZLFosFq9XK+OFwwHVd6rq+A3KzXsGlBJHQZylt3aKkwmrqht3PV97fj9i2zXa75Xg8GnYaKM9zmqZhtqGnE4IyjIiThKRqEJ0kqgRlV2HpU33fp+8Hw2Bi0vc9XdchpZyxylQQfDiEaUhaC1pV0MocJTMYGxh7LA2mGU1Az6woK4IkQiQOrRSMtID6I2fS1CqKwgivGT0KbxhVLc7bntQ/oKTQ9c67x/GvS7q5pdmdTqd/wKI45Xx6pa/jO4sHEB5yHyuz2rbF87w5yYB5Z1x3PzN6xuQzeSwtuuM41wscIShTQsc2dV3PGD4FfFqybomPG2BcJdTyAiLmf83SHz+MKUqPtzhGNi1t4JqW0XIopeYWmmI9TnNdoSalXccG8OicWa5fCBNB4Hu8fnsxuurnt9/v2Ww2ptHDMDRz/Xr02m63w97ZJEli1jSodVV9YBzuIqt+eLjZkTiOqarquqbu/dcPil+XE5eqIS8u5EXJb4xo7s4v5VgrAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Stargazer Statistics\"\n        title=\"A line graph from Star-History.com showing the growth of GitHub stargazers for beyondcode/laravel-websockets, swooletw/laravel-swoole, and hhxsv5/laravel-s\"\n        src=\"/static/f1c1ec6871ccc541e473d90056b04233/913fc/stargazer_statistics.png\"\n        srcset=\"/static/f1c1ec6871ccc541e473d90056b04233/bc34b/stargazer_statistics.png 293w,\n/static/f1c1ec6871ccc541e473d90056b04233/da9f0/stargazer_statistics.png 585w,\n/static/f1c1ec6871ccc541e473d90056b04233/913fc/stargazer_statistics.png 1170w,\n/static/f1c1ec6871ccc541e473d90056b04233/efb0c/stargazer_statistics.png 1755w,\n/static/f1c1ec6871ccc541e473d90056b04233/79b58/stargazer_statistics.png 2340w,\n/static/f1c1ec6871ccc541e473d90056b04233/6b6b8/stargazer_statistics.png 2424w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>On the extension-level side we have two libraries that are built around Swoole: <a href=\"https://github.com/swooletw/laravel-swoole\">swooletw/laravel-swoole</a> and <a href=\"https://github.com/hhxsv5/laravel-s\">hhsxv5/laravel-s</a>. They are both established packages with a wide contributor base, but my colleagues and I ultimately concluded that the sandbox and safety features within laravel-swoole were a bit more mature and reliable in our testing.</p>\n<p>There are a few blog posts out there on getting started with laravel-s but not as many for laravel-swoole, so I felt particularly motivated to write and publish this article. The laravel-swoole default WebSocket implementation is built with Socket.io in mind, where laravel-s is a bit more agnostic in its implementation. Both packages have some feature overlap but some differences as well. I would consider both of them for your project and evaluate the best fit!</p>\n<h1>Getting started with swooletw/laravel-swoole</h1>\n<p>First, let’s install either Swoole or OpenSwoole. I’ll be using Swoole for this example. Be sure to enable support for WebSockets on installation when prompted like so:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/34239a1147eeae2a84d2a91d9dee6f08/234d6/enable_sockets_support.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 69.24019607843137%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbElEQVQ4y5WTy07bQBSGj28TJzVJPPFl7PElCSKIl4AH6bZdURZdIBA0gbBqFrxJX6GLIvUBiNRuu6/UykKoLcT2qc5EUFouUi39OiMf65/vPzMGxAXg9Vf+E7+kP35dS0T8S1dXV/Li4kJeXl7e691RgogdRAR62OGrl+/H09eLwzfj7ycnJ8V4PC6Ojo6K4+Pj4uDgoNjZ2Sm2t7eLvb29Yjqdqj71JpNJsbu7+21/f7+eTCbvlKHzrOl2Of/kdjm22210XRebzaaqJM/z1PuVlRXsdruqBwD3lOf5RwAwiJDneX6e5xkmSbLo9/tVHMfVcDisRmujKk3TanV1VVXOeWXbdqVp2q10XV+Yponr6+sfAMAE0zRdKeU8jmMMw7AMggCFEJhlGdK61+thmqbY6XQeJAOAkupobe1MEVqWxX3fn5MJ57ykiL7vq7itVgsdx1FxaU2ybVtVit5oNKiWtNnGxsbSUNd1HkXRPEkSlFKW/X6fSJGIiXI4HCrCmzURk6lhGKjrOqlkjFHvD2Ecx4owCIIyiiJ1ELGUmKUpilAo8xtqMvs3Mhlvbm4uDZnFFCEZkSHNjQjzPMcwjrDLuaLjnD86Q6Ld2to6uzllN8uyORlQ5CRJaill7YuwTtudOmt3arvVqpnFagB4SAtN03AwGCwNDcPgQohzIvN9fyGEqDzPqyzGKhNACZ7WNRneXhsiTNP0M82QRIdD87JMcxlJ01DXNNQeEX1DkUej0e3FtsMwfOF53inn/G0QBLNerzdzHGfGGJs1Gg1Vn9Bb27ZPhRDPlWEYhmSqA4BF//V/yrpTtcFgAL8BHNRHajY+CAcAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Enable Sockets Support\"\n        title=\"Screenshot of prompt to enable sockets support when installing Swoole in terminal\"\n        src=\"/static/34239a1147eeae2a84d2a91d9dee6f08/913fc/enable_sockets_support.png\"\n        srcset=\"/static/34239a1147eeae2a84d2a91d9dee6f08/bc34b/enable_sockets_support.png 293w,\n/static/34239a1147eeae2a84d2a91d9dee6f08/da9f0/enable_sockets_support.png 585w,\n/static/34239a1147eeae2a84d2a91d9dee6f08/913fc/enable_sockets_support.png 1170w,\n/static/34239a1147eeae2a84d2a91d9dee6f08/234d6/enable_sockets_support.png 1632w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">pecl install swoole</code></pre></div>\n<p>If you run into any issues during the Swoole build process I included a few troubleshooting steps in the <a href=\"#appendix\">appendix</a>.</p>\n<p>Now, create a new Laravel app. Continue bootstrapping the app using <a href=\"https://jetstream.laravel.com/2.x\">Laravel Jetstream</a>. I used the Intertia template, but I believe the API feature should work with Livewire as well.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">laravel new\ncomposer require laravel/jetstream\nphp artisan jetstream:install inertia</code></pre></div>\n<p>Let’s set up that feature: just enable it by uncommenting the line in <code class=\"language-text\">config/jetstream.php</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token single-quoted-string string\">'features'</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> <span class=\"token punctuation\">[</span>\n    <span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span>\n    Features<span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span><span class=\"token function\">api</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span><span class=\"token punctuation\">.</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>Next, require the laravel-swoole package and publish its config files:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">composer require swooletw/laravel-swoole\nphp artisan vendor:publish --tag<span class=\"token operator\">=</span>laravel-swoole</code></pre></div>\n<p>Here we’ll also want to adjust the config to enable the WebSocket server by default.</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token single-quoted-string string\">'websocket'</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> <span class=\"token punctuation\">[</span>\n    <span class=\"token single-quoted-string string\">'enabled'</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> <span class=\"token function\">env</span><span class=\"token punctuation\">(</span><span class=\"token single-quoted-string string\">'SWOOLE_HTTP_WEBSOCKET'</span><span class=\"token punctuation\">,</span> <span class=\"token boolean constant\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">]</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>Now, for this basic example we’ll just have our handlers defined inline as closures in the websockets “routes” definition. For anything that is more than a simple exploration I would recommend creating controllers or <a href=\"https://laravelactions.com/\">Laravel Actions</a> to store the handler code.</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token php language-php\"><span class=\"token delimiter important\">&lt;?php</span>\n\n<span class=\"token keyword\">declare</span><span class=\"token punctuation\">(</span>strict_types<span class=\"token operator\">=</span><span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">use</span> <span class=\"token package\">App<span class=\"token punctuation\">\\</span>Models<span class=\"token punctuation\">\\</span>User</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">use</span> <span class=\"token package\">Laravel<span class=\"token punctuation\">\\</span>Sanctum<span class=\"token punctuation\">\\</span>PersonalAccessToken</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">use</span> <span class=\"token package\">SwooleTW<span class=\"token punctuation\">\\</span>Http<span class=\"token punctuation\">\\</span>Websocket<span class=\"token punctuation\">\\</span>Facades<span class=\"token punctuation\">\\</span>Websocket</span><span class=\"token punctuation\">;</span>\n\nWebsocket<span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token single-quoted-string string\">'whoami'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span>SwooleTW\\<span class=\"token package\">Http<span class=\"token punctuation\">\\</span>Websocket<span class=\"token punctuation\">\\</span>Websocket</span> <span class=\"token variable\">$websocket</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token variable\">$websocket</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token function\">emit</span><span class=\"token punctuation\">(</span>\n        <span class=\"token single-quoted-string string\">'message'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token variable\">$websocket</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token function\">getUserId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">===</span> <span class=\"token constant\">null</span>\n            <span class=\"token operator\">?</span> <span class=\"token single-quoted-string string\">'You are not authenticated'</span>\n            <span class=\"token punctuation\">:</span> <span class=\"token double-quoted-string string\">\"Your userID is <span class=\"token interpolation\"><span class=\"token punctuation\">{</span><span class=\"token variable\">$websocket</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token function\">getUserId</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">}</span></span>\"</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\nWebsocket<span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span><span class=\"token function\">on</span><span class=\"token punctuation\">(</span><span class=\"token single-quoted-string string\">'login'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">function</span> <span class=\"token punctuation\">(</span>SwooleTW\\<span class=\"token package\">Http<span class=\"token punctuation\">\\</span>Websocket<span class=\"token punctuation\">\\</span>Websocket</span> <span class=\"token variable\">$websocket</span><span class=\"token punctuation\">,</span> mixed <span class=\"token variable\">$data</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">is_string</span><span class=\"token punctuation\">(</span><span class=\"token variable\">$data</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token variable\">$tokenInstance</span> <span class=\"token operator\">=</span> PersonalAccessToken<span class=\"token punctuation\">:</span><span class=\"token punctuation\">:</span><span class=\"token function\">findToken</span><span class=\"token punctuation\">(</span><span class=\"token variable\">$data</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>\n            <span class=\"token variable\">$tokenInstance</span> <span class=\"token keyword\">instanceof</span> <span class=\"token class-name\">PersonalAccessToken</span> <span class=\"token operator\">&amp;&amp;</span>\n            <span class=\"token variable\">$tokenInstance</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token property\">tokenable</span> <span class=\"token keyword\">instanceof</span> <span class=\"token class-name\">User</span>\n        <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n            <span class=\"token variable\">$websocket</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token function\">loginUsing</span><span class=\"token punctuation\">(</span><span class=\"token variable\">$tokenInstance</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token property\">tokenable</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n            <span class=\"token variable\">$websocket</span><span class=\"token operator\">-</span><span class=\"token operator\">></span><span class=\"token function\">emit</span><span class=\"token punctuation\">(</span><span class=\"token single-quoted-string string\">'message'</span><span class=\"token punctuation\">,</span> <span class=\"token variable\">$tokenInstance</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span></code></pre></div>\n<p>Now we can test it out! Let’s start the Swoole server and test things out.</p>\n<p>Note: You will need to disable Xdebug before you can run any Swoole code. If you skip this step your terminal will be overwhelmed with warning logs when you run this next command and things might not behave properly. If you expect to be doing a lot of Swoole development then you may want to configure a tool or script to enable or disable loading the Xdebug extension. For macOS there’s an open source tool to add a toggle button in the menu bar called <a href=\"https://github.com/deligoez/xDebug-Toggler\">xDebugToggler</a>.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">php artisan swoole:http start</code></pre></div>\n<p>Try navigating to <a href=\"http://127.0.0.1:1215\"><code class=\"language-text\">http://127.0.0.1:1215</code></a> and seeing if you are able to register and login just like if you were to run <code class=\"language-text\">php artisan serve</code>. From there, head to the API Tokens section:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/dadfb062a5e2d828215d2be8e224afda/a40cf/jetstream_api_tokens.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.84584342211461%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC2ElEQVQ4y5WS327TSBTGJ8kmrWhJS/mjrlQt2qqiEhJS+wp7gQJ3RrwBl0T7MsteobY0C4Ir+gfERW/oIvYNYEWSuiF1qu0m8dgzsceOPTMfGrugctELLP30feeMdXzO8ZBfqnOX5q9duDdZKVo3l29YKyur1vLysrW0tGRNT09b5XLZqlQq3zAxIcQqFotWgZDMG34qle5PTk4ukJ8vX7195fJFXJqp4tfr17G4uIiFhQXMz89jYmICxWIRpVIpw/hyuYxqtYqpqWlUZ69idnYGc3NzuDhTNfmH5PHjR3fevmvhU9OWHz58lK1WS7XbbWXU0Gy2zvhmpp1OR9n2oep0HWXbtjw4OEha7RbW1tbq5M2b17VQpAjCsQKgfxSdqwSAvb29Onn+/EXN8xnCcKSSNNbxOEUcjxEZohhhGOWI6Hs/ChAMXIRRlBUUQmB7e6dO1jcatfE4Qn+o1Wcn0oP+CQZDF57ng3MOqRSkVEhT+R0mF4/HCIXQSkk5CkJsmYJr65u1JIlBPa26TqD7/f/guhSe52UFkySBUmYb5z5Zh2EosLW1XScbG5s1M5pMx4rSgWaMgzEG13VBKcXJyQl8xiCl6SxFkqSZnvF5QRHlBdc3NmtxHJsxlEuZpp4P6rFvMB6A8RHMng2U5ufGu9SH73OttZZCRHj5cqtOnjxp1EIhkEqphIh0FMf5T4nirAMzrsr2KLN9KqXznNZffdZhEArs7OzWSaPxtJZ/ncv+kCrqMRWKSAWhQahRILKY8UANXU/5jGexyZ85T/kowP7+3w9J469nd80VCaMYA9cDzYuDnuJ6LLtCUmm4lOP/gY8hNXkOj5lVjCBOr9n79//8Tv549Odvnc9dcWAfsY//HrJPzS4/7PS40zvmjmO0x4+cHNt2eLvtZHp0euY4PdY7PvYPO910d/fVA0IImSKE3CKksEpIYYUQci7lcmGlMlHI9Jx3Zr8A2ixLZNOtMRMAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Jetstream API Tokens\"\n        title=\"Screenshot of Laravel Jetstream landing page with the navigation menu opened and the API Tokens nav item highlighted.\"\n        src=\"/static/dadfb062a5e2d828215d2be8e224afda/913fc/jetstream_api_tokens.png\"\n        srcset=\"/static/dadfb062a5e2d828215d2be8e224afda/bc34b/jetstream_api_tokens.png 293w,\n/static/dadfb062a5e2d828215d2be8e224afda/da9f0/jetstream_api_tokens.png 585w,\n/static/dadfb062a5e2d828215d2be8e224afda/913fc/jetstream_api_tokens.png 1170w,\n/static/dadfb062a5e2d828215d2be8e224afda/efb0c/jetstream_api_tokens.png 1755w,\n/static/dadfb062a5e2d828215d2be8e224afda/79b58/jetstream_api_tokens.png 2340w,\n/static/dadfb062a5e2d828215d2be8e224afda/a40cf/jetstream_api_tokens.png 2478w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>Create a token and save it for the next step.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/0d4797c19927810f9c1900df3b07b583/a40cf/create_api_token.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.84584342211461%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACb0lEQVQ4y6XRTU8TQRgH8IctW6CVLVAxkBRNiNh4MIGv4MFUb2v8Bh5p/DQmxHAyQouJIfH1JJRiJB5ENBqxLd0FYwnGvuz77uzuzGNmKdV4IFE3+eWZmWT+z8wsnJfGRifOJW4OxgX5cnZGnp2dk7PZrHxx5pKclFJyPB6XBwYGosqJoigDgCwIgtwHIENfnwwxUe7vF28NDQ1mYDI9fu1sehhHUxJemJrC6elpzGQyODExiYlEAgVBwFgsFuFjURRRkiRMJs+gNDKOqVQKx9JpHE5JfH0eFhfvXi+VK1ipKuHul92wWq3SWq3WU6lUKF/jTsaqqtJ6vU6V/a9UUZSwtrfnV2s1XF1dzcOL589ythMiIQFFRPaPQkTEnZ2dPBSLKzlN01DTDWrZDnNcDx2HcyMun7tub/47+xjfE1qWg+XyZh6KKw9zjcPvqB406LfGEfvR6mCzpWGrraPteBFCAgwC2uN3a0gpUkoZYyz0PILrpXIelgvFHA9otjXabGus2eqgpluoGxbaDsF2R0fTcjAMEYOusIvxy3avTIiPpY1yHh4sLec03UTLdqlpOcyy3SiAcz2Ctu0e72K/6LrPT4bdLwpkjOHa+kYelpYKUaBpOdQwbWaYdhTGq0d89AiJNvcwFjUKgiBKIsRnHz5+DhuHR7i19eb0QP7ojkt6b3kiOnlUfWx1NHa/8Ch89/4Tvt3ePr5yRzN4QGiYNv2Tblin4gdBxIA3frlWmodCceUGfzePBFHHf2E5Hv8HuPnq9R1YWLh3da+uOop6oCvqPmf8JV1RD7S6sh88fvL0NgBAEgCuAMAcAMz+p5GfZ05M9YjDnygAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Create API Token\"\n        title=\"Screenshot of Jetstream page for creating an API token\"\n        src=\"/static/0d4797c19927810f9c1900df3b07b583/913fc/create_api_token.png\"\n        srcset=\"/static/0d4797c19927810f9c1900df3b07b583/bc34b/create_api_token.png 293w,\n/static/0d4797c19927810f9c1900df3b07b583/da9f0/create_api_token.png 585w,\n/static/0d4797c19927810f9c1900df3b07b583/913fc/create_api_token.png 1170w,\n/static/0d4797c19927810f9c1900df3b07b583/efb0c/create_api_token.png 1755w,\n/static/0d4797c19927810f9c1900df3b07b583/79b58/create_api_token.png 2340w,\n/static/0d4797c19927810f9c1900df3b07b583/a40cf/create_api_token.png 2478w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>Now, let’s connect to the server via WebSocket and test our handlers! Open up your WebSocket client of choice. I’ll be using <a href=\"https://www.postman.com/\">Postman</a> here.</p>\n<p>Let’s get connected and make sure everything works. Enter your hostname and port (likely <code class=\"language-text\">127.0.0.1:1215</code>) and hit connect. You should see two messages come through and the status should still read <code class=\"language-text\">CONNECTED</code>.</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/5d2d1fe17ad91fc6d18b15ed17a4d861/d2512/websocket-connect.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACOklEQVQ4y32SS2/TQBDHLVqUOLmD1MTe3Zl9eWMnTpoKtRKCW8SRG5/AKRKfmlbixAegtT1oNjGlVdvDT/uYnf+8NtkUHjYr/aMKrtmslvtQFPuqqvZlWUastZH1er3fbrfRprWOd965/eery31d13x/jYjvkmD0dV15Wi+XtK1XFEJBRVGQc46stQQAET5778kYQ0opQsR4rpfLaGMfANglqLImNzVlxeUfZ027WJRtuVi01tpWStkqpSK8H86I2BpjWudcG0K4997faa1JSrlLtNYNZ+K8v9dG94jYA8CrKKUieZ73IYT+4uKiY408z3cJADScblVVrbam50hcIpf1HFyydZ6KItC6rrktLN6xX5ZlB0HuByC2SqpeSvmiGMPBsrP3tAiBzs/PiTM0xnSokWZns10ihIiCqDX3qB8cB+GnAaQU9Onrnsr1B8rnM8pB9gJkB0rRfD7fJfMsa7x3HJEb3g9THUrnEnll4QOCys0VoXGkhCARsM8L6EABzVhQiqyRYAi0b7XG2EOGhf5fYxVH8mwWheP3UdAb1B0HjRnK/CBo/KJdVmW/WvFfDPEPxuk79+8PMrxXCiKcsTGm994/CIpcNMdm3yFix/DUpJQvwvYn3D9kiOq7etSj16c8DOopR8EvibL4TVv9GwB+IuItANwqpW6FEM/CNn7Db4/cAMANIv7Ksuxj8jZ5M52MUz2ZTMx0OtVpmprRaGROTk7M6enpI/huPB6b49thjb5pmsJoNBr/BWn9Vi0wXoNpAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Connect to WebSocket with Postman\"\n        title=\"Screenshot of Postman configured with the hostname and port of the local example WebSocket server\"\n        src=\"/static/5d2d1fe17ad91fc6d18b15ed17a4d861/913fc/websocket-connect.png\"\n        srcset=\"/static/5d2d1fe17ad91fc6d18b15ed17a4d861/bc34b/websocket-connect.png 293w,\n/static/5d2d1fe17ad91fc6d18b15ed17a4d861/da9f0/websocket-connect.png 585w,\n/static/5d2d1fe17ad91fc6d18b15ed17a4d861/913fc/websocket-connect.png 1170w,\n/static/5d2d1fe17ad91fc6d18b15ed17a4d861/efb0c/websocket-connect.png 1755w,\n/static/5d2d1fe17ad91fc6d18b15ed17a4d861/79b58/websocket-connect.png 2340w,\n/static/5d2d1fe17ad91fc6d18b15ed17a4d861/d2512/websocket-connect.png 2484w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>If you’re wondering what those 1-2 digit numbers prefixing the server messages are, those are <a href=\"https://github.com/socketio/socket.io-protocol#packet-encoding\">Socket.io packet type encodings</a>. You can remove these and adjust anything about frame parsing by creating your own implementation of <code class=\"language-text\">\\SwooleTW\\Http\\Websocket\\Parser</code>.</p>\n<p>Let’s send a <code class=\"language-text\">whoami</code> payload using this JSON:</p>\n<div class=\"gatsby-highlight\" data-language=\"json\"><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">[</span>\n  <span class=\"token string\">\"whoami\"</span>\n<span class=\"token punctuation\">]</span></code></pre></div>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/68d7813ef9dd52c4ff2b9ee969eb463e/d2512/whoami_no_auth.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACSElEQVQ4y4VS227UMBCNaNHe1FeE2o3t8fgSO8neuqq0SEjwtOIP+ACULRJfTSvxxAfQJhk0prvdtiAejsZz8fGcGWerUOjV3Hyro29W89kuhrCr63pXVVWCcy5huVzu1ut1yhljUqzwfvfh3Wa3WCw4fo2Ib7JozfWiLmg5m9F6MacYA4UQyHtPzjnSWiewXxQFWWsJAAgRk7+YzVKO72ittxlC3gi7oDxsfnln27Ks2qosW+dcq5RqASCBz3sfEVtrbeu9b2OM9977O2MMKaW2mTGm4U6KItwba3pE7LXW/wUA9FLKPsbYX11dddyllHKbaa0bbjeG0DIhv8QSWdZzsEzOo3XkfUF1XXOsF0J0nDsQsuOcbRGgV0r9lewJsXhL81lFq8tLKmPZO2s7NEjn5+fbTAjRIBoeNs+nf375xQNKkvj4hdx8Q6gEgcUenelQI11cXGyzaZ43vK2yjDz0RMiS99J5q6zgQKwUyWpDCj2BlCSC7vMCOg2aptPpNpNSNKCRjPNtGWO//x4xxgPZi5mKi9SpYvkae4um45o/hEI0WiM57/kbHCTvSbiz5wB4zPEivfePhEpMG6ktoYt3oSi6sqy6EAJ/g845l2xRFAewDwBMkKwxhuvuHwk1fAWDZJ1LMo+l/gvHC2Of6x8IP2Xg8LOx5qcG+A5a3yqlEoQQt1LKZI8BALeIeIwbrfUNIv7I8/x99jp7NRkPR2Yymdizs7NkB4OBPTk5saenp0/AseFwaMfjseW6B5vujEYjPRgMhr8BkIFW1gyrwQcAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"WebSocket whoami test\"\n        title=\"Screenshot of Postman client after sending a whoami WebSocket message, with the server returning an unauthenticated message response\"\n        src=\"/static/68d7813ef9dd52c4ff2b9ee969eb463e/913fc/whoami_no_auth.png\"\n        srcset=\"/static/68d7813ef9dd52c4ff2b9ee969eb463e/bc34b/whoami_no_auth.png 293w,\n/static/68d7813ef9dd52c4ff2b9ee969eb463e/da9f0/whoami_no_auth.png 585w,\n/static/68d7813ef9dd52c4ff2b9ee969eb463e/913fc/whoami_no_auth.png 1170w,\n/static/68d7813ef9dd52c4ff2b9ee969eb463e/efb0c/whoami_no_auth.png 1755w,\n/static/68d7813ef9dd52c4ff2b9ee969eb463e/79b58/whoami_no_auth.png 2340w,\n/static/68d7813ef9dd52c4ff2b9ee969eb463e/d2512/whoami_no_auth.png 2484w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>As expected we get the unauthenticated message. So let’s log in! Refer to the following JSON payload:</p>\n<div class=\"gatsby-highlight\" data-language=\"json\"><pre class=\"language-json\"><code class=\"language-json\"><span class=\"token punctuation\">[</span>\n  <span class=\"token string\">\"login\"</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">\"YOUR_API_TOKEN\"</span>\n<span class=\"token punctuation\">]</span></code></pre></div>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/1261d6f17d687f82a88fc097364c84a9/d2512/login.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACWklEQVQ4y4WS227TQBCGXbWSm+QeiSb2HmZ29hA7daKqoggkxE3EG/ACOEXiqaESVzwA1PagWUjVokpcfBrPjvzvzD9b7GKwu0v80ibf7y43hxTjoW3bQ9M0GSLKbLfbw9XVVa4hYj4L3h/evb45dF0n57cA8KJIDm+7NvB2s+Gr7pJTihxjZO89ExFbazOShxDYOcfGGAaAnHebTa7JP9bafQGm6mvXcRVufnpyw3rdDM16PRDRoLUejDEZ+T7mADA45wbv/ZBSuvfe/3LkWWu9LxCxl05CjPfocAKAyVr7X4wxk1JqSilN19fXow+R66raFxagjzFI2wOIIGIeSRuT42NkTERkQGIfArdtK2dTXdcjOuIqC1rbZz88DdHhRAjsHWbA2ueF65fcpMgb8Y9oMtqMgI6Xy+W+qOu6l1vFM7B2EhHIizBspVOtnwpqxfX7Txx3b5jQsgt+ouhH8fBiebEvVlXVy7ZSSmL6pB+NJ1Euky2LcEYprtevWAGxUYpVgklFOwI4Xq6W+0KpujcWGJ1sLE5/xR9omiY/C0GejNQJNHtyLH6jhQktZA9Xq9W+UPUfQfJhaJpmEoGu6zJiughIt88hExDRRER55CwoHspIRPQrhDBaa0djTI5a61EpleNjpH7EOTci4v2DoLHqs0ZipCSbfjKeIG/0X44LEk+PZ+iy4IfCkP0IRD8A3Vfv6c57f+ecuwOAJyBijsfaMUfEbwIgfq+q6m1xVpwt5mWJs1npyrLE09NTd3Jy4oqieBapz2Yzt1gs3Hw+l2+U/Py8tGVZnv8GQ1BYPV7HwjwAAAAASUVORK5CYII='); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"WebSocket login test\"\n        title=\"Screenshot of Postman client after sending a login WebSocket message, with the server returning additional token information\"\n        src=\"/static/1261d6f17d687f82a88fc097364c84a9/913fc/login.png\"\n        srcset=\"/static/1261d6f17d687f82a88fc097364c84a9/bc34b/login.png 293w,\n/static/1261d6f17d687f82a88fc097364c84a9/da9f0/login.png 585w,\n/static/1261d6f17d687f82a88fc097364c84a9/913fc/login.png 1170w,\n/static/1261d6f17d687f82a88fc097364c84a9/efb0c/login.png 1755w,\n/static/1261d6f17d687f82a88fc097364c84a9/79b58/login.png 2340w,\n/static/1261d6f17d687f82a88fc097364c84a9/d2512/login.png 2484w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>And we get the <code class=\"language-text\">$tokenInstance</code> we emitted in our <code class=\"language-text\">routes/websocket.php</code> handler for <code class=\"language-text\">login</code>, and the default parser was nice enough to JSON-encode it for us! We can send another <code class=\"language-text\">whoami</code> to check our work:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/bd66dadb36c7f5737067d4ddf7db0884/d2512/whoami_with_auth.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACXklEQVQ4y4VT227bMAw11naOk/dhaGNZEkXJli+5NCiQAQO2p2B/sA8Ykg7YV28B9rQPWGuLAxU4Q9cCezigSVqHPKSUrKtSrxfmW+vdfr3oDr6qDm3bHpqmibDWRqxWq8Nms4k5Y0yMlc4dPrzbHpbLJcfvAeBN4tHcL9uSVl1Hm+WCvK+oqipyzpG1lrTWEeyXZUmISEopAoDoL7su5viM1nqXgMr3ApeUV9vfzmJf103f1HVvre2llL1SKoK/Rx8AekTsnXO99/7ROfdgjCEp5S4xxuy5k7KsHg2aAABBa/1fKKVCURTBex/u7u4G5hBC7BKt9Z7b9VXVMyFXYoks61+wTM4DWnKupLZtORaEEAPn8jw/EbJjLfagVJBSvkj2hFi8pUXX0Pr2lmpfB4s4cKHr6+tdIoTYAxgeNs8njIdG4mcFZEHi4xeyiy2BFKQQAlgzgAa6ubnZJfM83/O26rrmoUdClhylAcStsv1LKKlotiTBkSoKEpUOolSDVorm8/kuKQqxVxrIWN5YFfgKMJhwnNvY6Rn5NcmiICkVGQ0BNMQZngjFidC5sm+aJnBHvDEmZLA/xtiO95MRFVgb0No4wzMhS0RrH7z3Q13XQ9d1EW3bDhwzxjwDInJX0SLi45lQavlVG4iVm6Y5vwjv/fmlvLTpUf7YrTrN8FMiLXxGC7/Q6O+I9qi1Pkopj3meH4UQx6IonoDzAHA0xoz2BwMAfuZ5/j55nbyaTdKpSbMpZpOJmU6nOJlM8OrqCi8vL/Hi4uIJ0jTFLMtwNpsh/5tlmWE/yzKdpunkD4I+V6B1bewiAAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"WebSocket whoami second test\"\n        title=\"Screenshot of Postman client after sending a whoami WebSocket message, with the server returning the authenticated user's id\"\n        src=\"/static/bd66dadb36c7f5737067d4ddf7db0884/913fc/whoami_with_auth.png\"\n        srcset=\"/static/bd66dadb36c7f5737067d4ddf7db0884/bc34b/whoami_with_auth.png 293w,\n/static/bd66dadb36c7f5737067d4ddf7db0884/da9f0/whoami_with_auth.png 585w,\n/static/bd66dadb36c7f5737067d4ddf7db0884/913fc/whoami_with_auth.png 1170w,\n/static/bd66dadb36c7f5737067d4ddf7db0884/efb0c/whoami_with_auth.png 1755w,\n/static/bd66dadb36c7f5737067d4ddf7db0884/79b58/whoami_with_auth.png 2340w,\n/static/bd66dadb36c7f5737067d4ddf7db0884/d2512/whoami_with_auth.png 2484w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>We now have a WebSocket server and a way to authenticate connections! This example is very basic, but I hope it gives a background to build something great with.</p>\n<h1 id=\"appendix\">Appendix</h1>\n<h2>Notes on using Swoole and laravel-swoole</h2>\n<h3>Try Using Swoole (or RoadRunner) to serve your app</h3>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/4dddb8323d9710d1a92cea6578e78a2c/0b97c/swoole_http_benchmark.png\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 51.40728476821192%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACK0lEQVQoz11QTW/TQBCdjR3v2mt7vf6Is0naUJyK1kU9VOqlEscWxIWfwD+o1ENz6KWnFIGSuBI3ZMQf4wxF3KCnJs6g3RLUcnh6M5o3b98OvH39Rlbvxx+n8w9fZtNZPZ/P6tlsVk+n0/r6+rqeTCb15eVlfTW5qt9NrupqXj1GVWnd56qqPo3H4wKigO/mw+eYP3mK6aCPeX8TB12FvX4P0zRFz/Vwc2sT+3tbKAuFsuhhkueYZTEmnQTTJMU4jjHLMjw4OHgFnHs7Xqh+hYKvhKB3QcAWjtNeOI6zAACDNEsXFrPu+xYsgDzge82SUro8PDw8hiiSO4zZt1mWogjFCgD+gRBiOI7jFQHyaPYfMAxDPD8/PwEp5a4Q0a3+nhBiRSlF13WRMWZY90opTNIEfd/HIAgMtMFfXnHOcTAY4Onp6QmEkdhVPXWrl/I8X3W7XXMPbahh2zaqnsLt7W0cDocGGxsb2Ol0jKk21A/pQPv7+ydg2/aOEOK3NpFSLpMkaYIgaHzfbwghDQA0Usomz/MmiqImjuNGazjnDWNMz5daQyltjo6OjiEIg1IpdSelxCiKzDc8zzPpHMdBfZ+yLLEoCnMCnUqfQScnhJi5ht6/uLh4qQ0L5rKvVqv107bt74SQGwC40dxqtUw9Go1uwjA09Rp6/kDzQ0r57ezs7AUAQNu2rJHrunuU0tLzvJJzbnhdW5ZVttvt0vd90695rfE8T+8+AwD2B6Ih3fwCh/e9AAAAAElFTkSuQmCC'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Swoole benchmark comparison\"\n        title=\"Screenshot of wrk benchmarks against artisan serve and Swoole, showing 36.55 requests per second for artisan serve and 218.66 for Swoole\"\n        src=\"/static/4dddb8323d9710d1a92cea6578e78a2c/913fc/swoole_http_benchmark.png\"\n        srcset=\"/static/4dddb8323d9710d1a92cea6578e78a2c/bc34b/swoole_http_benchmark.png 293w,\n/static/4dddb8323d9710d1a92cea6578e78a2c/da9f0/swoole_http_benchmark.png 585w,\n/static/4dddb8323d9710d1a92cea6578e78a2c/913fc/swoole_http_benchmark.png 1170w,\n/static/4dddb8323d9710d1a92cea6578e78a2c/efb0c/swoole_http_benchmark.png 1755w,\n/static/4dddb8323d9710d1a92cea6578e78a2c/79b58/swoole_http_benchmark.png 2340w,\n/static/4dddb8323d9710d1a92cea6578e78a2c/0b97c/swoole_http_benchmark.png 2416w\"\n        sizes=\"(max-width: 1170px) 100vw, 1170px\"\n      />\n  </span>\n  </a></p>\n<p>If you end up using a Swoole-based solution for your WebSocket server, why not serve HTTP requests using Swoole as well? Laravel’s request lifecycle by default is actually pretty redundant: for each incoming request that your Laravel app handles it needs to be fully bootstrapped every time: framework, service providers and all. There is a first-party solution to this: <a href=\"https://laravel.com/docs/9.x/octane\">Laravel Octane</a>, which uses your choice of Swoole (or OpenSwoole) or RoadRunner to provide a relatively easy way to serve many more requests by booting and loading the application into memory. Laravel Octane with RoadRunner is very likely to be a drop-in replacement for Nginx/PHP FPM for your Laravel app if you’d rather not add Swoole to your stack. Either way, it could be an easy win to dramatically increase your throughput for typical HTTP server needs using Laravel Octane, laravel-swoole, laravel-s, or another similar solution.</p>\n<h3>Choosing a WebSocket Room Driver</h3>\n<p>Be sure to choose your room driver, which associates WebSocket connections with users as well as membership in channels, appropriately for your use case. By default, the library will use a Swoole table to store these records. Swoole tables are an incredibly performant data store compared to Redis, as <a href=\"https://cerwyn.medium.com/whos-the-winner-redis-swoole-table-or-local-cache-ec315487db04\">Cerwyn Cahyono concluded</a> in May 2021. One consideration however is horizontal scaling: if you have more than one WebSocket server behind a load balancer, for instance, you need to consider that Swoole tables are only accessible by the Swoole server process and its forks.</p>\n<p>If you need to have a common record among all of your WebSocket server instances with laravel-swoole then you may want to consider the provided Redis driver (<code class=\"language-text\">\\SwooleTW\\Http\\Websocket\\Rooms\\RedisRoom</code>), or creating your own implementation. Be sure to add a prefix unique to the server for all records, that way you don’t end up sending a message intended for one WebSocket connection on Server A to another, unrelated WebSocket connection on Server B.</p>\n<h3>Implementing middleware for WebSocket frames/messages</h3>\n<p>You may also find that you’d like to have middleware for all incoming WebSocket messages. If all you need to do is interact with parts of the Frame then you can add this handling in a custom handler or parser. These are defined in <code class=\"language-text\">config/swoole_websocket.php</code>. If you need to get the user ID and interact with a WebSocket instead then you’ll have to override and extend the <code class=\"language-text\">\\SwooleTW\\Http\\Concerns\\InteractsWithWebsocket</code> trait to directly modify the Swoole <code class=\"language-text\">onMessage</code> handler.</p>\n<h3>Adding Swoole to an existing stack/system</h3>\n<p>If you have a system where a Swoole WebSocket server stands alongside other pieces like an HTTP server, queue worker, etc. then you will need to implement some kind of global pub/sub infrastructure that coordinates between the Swoole WebSocket server and everything else. Redis is one way to fill that need. You could have an async Redis client boot with the Swoole server and <code class=\"language-text\">SUBSCRIBE</code> to a given channel. The client could listen for a JSON payload which could simply have a user ID and data to emit to the authenticated WebSocket connection. That way, you could issue WebSocket messages from non-Swoole contexts by simply <code class=\"language-text\">PUBLISH</code>ing the JSON payload to the corresponding channel. This does have the added overhead of having to establish a Redis connection for each emit from a non-Swoole context, but you get the flexibility of cooperating well with your existing system.</p>\n<h3>Troubleshooting installation of Swoole extension</h3>\n<p>When installing the Swoole extension on both ARM64 and x64 macOS machines I’ve run into a few issues and wanted to share how I resolved them.</p>\n<h4>fatal error: ‘pcre2.h’ file not found</h4>\n<p>If you get this error when installing Swoole then you need to be sure you have <code class=\"language-text\">pcre2</code> installed. On macOS, you can do this using Brew:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">brew <span class=\"token function\">install</span> pcre2</code></pre></div>\n<p>Then you can use the Brew CLI to grep the location of the <code class=\"language-text\">pcre2.h</code> file and link it under your current PHP version’s <code class=\"language-text\">include/php/ext/pcre/</code> directory.</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">brew list pcre2 <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> <span class=\"token string\">'pcre2\\.h$'</span>\n\n<span class=\"token function\">ln</span> -s /opt/homebrew/Cellar/pcre2/xx.yy/include/pcre2.h /opt/homebrew/Cellar/php@x.y/x.y.z/include/php/ext/pcre/pcre2.h</code></pre></div>\n<h4>fatal error: ‘openssl/ssl.h’ file not found</h4>\n<p>This guide doesn’t require building Swoole with OpenSSL support, but if you do you may need to set your OpenSSL directory during the build config. You can do so by first ensuring that you have OpenSSL installed locally, on macOS you can also do this using Brew:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">brew <span class=\"token function\">install</span> openssl</code></pre></div>\n<p>Then you can once again use the Brew CLI to get your OpenSSL directory and pass it in during the extension build configuration, right after executing <code class=\"language-text\">pecl install</code>. When prompted to enable OpenSSL support, type out “yes” and then add the <code class=\"language-text\">--with-openssl-dir</code> flag inline like so:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">brew --prefix openssl\npecl <span class=\"token function\">install</span> swoole\n\n\n<span class=\"token function\">enable</span> openssl support? <span class=\"token punctuation\">[</span>no<span class=\"token punctuation\">]</span> <span class=\"token keyword\">:</span> <span class=\"token function\">yes</span> --with-openssl-dir<span class=\"token operator\">=</span>/opt/homebrew/opt/openssl@3</code></pre></div>\n<h2>Notes on WebSockets</h2>\n<h3>Subprotocol gotcha</h3>\n<p>Be sure to pay attention to the <code class=\"language-text\">Sec-WebSocket-Protocol</code> HTTP header on the initial connection request. If the client request specifies a protocol in the header and the server doesn’t respond with that protocol, or any, then some browsers like Chrome will just drop the connection, which technically follows the WebSocket spec more closely, and others like Firefox and Safari will connect without hesitation.</p>\n<h3>Which WebSocket dev client to use?</h3>\n<p>For me this has been <a href=\"https://firecamp.io/\">Firecamp</a>, but I have gotten frustrated with the bugs and poor performance of the app. It has a lot of potential, so I’m definitely going to keep watching it! <a href=\"https://insomnia.rest/\">Insomnia</a> just added WebSocket support, but it is still immature and lacking features. As of Jan 2023 I recommend using <a href=\"https://www.postman.com/\">Postman</a>, though note that even for them WebSockets support is somewhat of a beta.</p>\n<h3>Give your WebSocket server a heart(beat)</h3>\n<p>Be sure to implement some kind of heartbeat function for persistent WebSocket connections. It’s a good general practice, and helps when you have or want infrastructure configurations that close inactive connections. With many subprotocols the heartbeat is client-initiated.</p>\n<h3>Authentication Patterns</h3>\n<p>There are many patterns for authenticating WebSocket connections, but can be categorized as either during the initial connection request (authentication-on-connection) or afterwards within the established connection (authentication-after-connection). During the initial connection request the two most common approaches are to either use a cookie, or a token in the header or body. Typically, with the authentication-on-connection approach the connection is closed by the server if the authentication check fails. With the authentication-after-connection approach typically servers have a timeout which closes connections that don’t authenticate within a given timeout. At Knack, we created a WebSocket backend that implemented the <a href=\"https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md\">graphql-transport-ws</a> subprotocol which frontend library <a href=\"https://github.com/enisdenjo/graphql-ws\">enisdenjo/graphql-ws</a> supports.</p>\n<p>For reference, laravel-swoole is configured by default for authentication and other middleware to be run on the initial connection request. While this is a valid approach, my impression is that the dominant WebSocket authentication pattern is to support authentication-after-connection. For this guide I implemented an authentication-after-connection flow as a barebones custom subprotocol. Before proceeding with your project be sure to consider what kinds of clients will be connecting to your WebSocket server and choose your approach accordingly.</p>\n<p>This article has been <a href=\"https://heymisha.com/blog/laravel-websocket-server-guide/\">cross-posted on my personal blog</a></p>","htmlAst":{"type":"root","children":[{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"WebSockets are a ubiquitous tool for building real-time experiences for users. They provide a persistent, bidirectional connection between a client and a server. For example, say the frontend of your app needs to stay up-to-date within seconds of your backend to do things like deliver notifications, status updates, etc. You may have implemented this as a periodic check, or poll, of the backend via some API like REST or GraphQL. Now that you have more users, and/or your processes have become more complex, these requests can start to weigh on your infrastructure capacity. That’s a problem that WebSockets can help solve."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Laravel and its ecosystem play a significant role in our stack at Knack, and recently we embarked on a new WebSocket server implementation which would work closely with our Laravel-based core API. For the uninitiated, "},{"type":"element","tagName":"a","properties":{"href":"https://laravel.com/"},"children":[{"type":"text","value":"Laravel"}]},{"type":"text","value":" is a popular PHP framework and ecosystem that provides a rich set of tools and features for building modern web applications. We decided to use the "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/swooletw/laravel-swoole"},"children":[{"type":"text","value":"swooletw/laravel-swoole"}]},{"type":"text","value":" library to build our server-side implementation of a custom subprotocol and I had the opportunity to document some of my experiences along the way. I’ll show how to get started with the library, provide some background information, some caveats to consider, and drop a few tips. Let’s get started!"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h2","properties":{},"children":[{"type":"text","value":"WebSocket Server Solutions for Laravel"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"First, we are working with the assumption that you want, or need, to host your own WebSocket server, and that you want to do so in PHP. With a stateful protocol like WebSockets we need to be able to run PHP concurrently. There are basically two different approaches: language-level and extension-level."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Language-level libraries and frameworks implement concurrency in pure PHP. Examples include "},{"type":"element","tagName":"a","properties":{"href":"https://amphp.org/"},"children":[{"type":"text","value":"AMPHP"}]},{"type":"text","value":" and "},{"type":"element","tagName":"a","properties":{"href":"https://reactphp.org/"},"children":[{"type":"text","value":"ReactPHP"}]},{"type":"text","value":". In contrast, extensions are written in a lower-level language, typically C/C++, and expose APIs at the PHP-level to access the extension-level functionality. "},{"type":"element","tagName":"a","properties":{"href":"https://www.swoole.com/"},"children":[{"type":"text","value":"Swoole"}]},{"type":"text","value":" and "},{"type":"element","tagName":"a","properties":{"href":"https://openswoole.com/"},"children":[{"type":"text","value":"OpenSwoole"}]},{"type":"text","value":" are examples of PHP extensions. In short, Swoole is an event-driven, asynchronous, multithreaded framework that makes it easy to build performant, concurrent servers."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now let’s evaluate pre-packaged WebSocket Server solutions that easily plug into a Laravel app. On the language-level side we have the seemingly most popular choice: the "},{"type":"element","tagName":"a","properties":{"href":"https://beyondco.de/docs/laravel-websockets"},"children":[{"type":"text","value":"beyondcode/laravel-websockets"}]},{"type":"text","value":", a package by Beyond Code. It is built on "},{"type":"element","tagName":"a","properties":{"href":"http://socketo.me/"},"children":[{"type":"text","value":"Ratchet"}]},{"type":"text","value":" (which is built on "},{"type":"element","tagName":"a","properties":{"href":"https://reactphp.org/"},"children":[{"type":"text","value":"ReactPHP"}]},{"type":"text","value":"), has a Pusher protocol implementation, and plugs well into the Laravel framework’s broadcasting API. While this supports a lot of use cases and does so in a familiar “Laravel way” it is not very flexible when you need something like a custom subprotocol implementation,  and it is inherently limited in performance due to being built on a PHP server core rather than a C/C++ one."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/f1c1ec6871ccc541e473d90056b04233/6b6b8/stargazer_statistics.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 63.53135313531353%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1ElEQVQ4y52TW4/aMBCF8///Fw9FKttSxIYC2rJZck+cC7nZSb7KhgTasi8daWJPPD4+czy2AIo8IwxCsiwjTVMzCiGIosjEwzDoNMZxvLuOp39KMpYZQ5ljdVLyY71m+fU7X5ZLFosFq9XK+OFwwHVd6rq+A3KzXsGlBJHQZylt3aKkwmrqht3PV97fj9i2zXa75Xg8GnYaKM9zmqZhtqGnE4IyjIiThKRqEJ0kqgRlV2HpU33fp+8Hw2Bi0vc9XdchpZyxylQQfDiEaUhaC1pV0MocJTMYGxh7LA2mGU1Az6woK4IkQiQOrRSMtID6I2fS1CqKwgivGT0KbxhVLc7bntQ/oKTQ9c67x/GvS7q5pdmdTqd/wKI45Xx6pa/jO4sHEB5yHyuz2rbF87w5yYB5Z1x3PzN6xuQzeSwtuuM41wscIShTQsc2dV3PGD4FfFqybomPG2BcJdTyAiLmf83SHz+MKUqPtzhGNi1t4JqW0XIopeYWmmI9TnNdoSalXccG8OicWa5fCBNB4Hu8fnsxuurnt9/v2Ww2ptHDMDRz/Xr02m63w97ZJEli1jSodVV9YBzuIqt+eLjZkTiOqarquqbu/dcPil+XE5eqIS8u5EXJb4xo7s4v5VgrAAAAAElFTkSuQmCC'); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Stargazer Statistics","title":"A line graph from Star-History.com showing the growth of GitHub stargazers for beyondcode/laravel-websockets, swooletw/laravel-swoole, and hhxsv5/laravel-s","src":"/static/f1c1ec6871ccc541e473d90056b04233/913fc/stargazer_statistics.png","srcSet":["/static/f1c1ec6871ccc541e473d90056b04233/bc34b/stargazer_statistics.png 293w","/static/f1c1ec6871ccc541e473d90056b04233/da9f0/stargazer_statistics.png 585w","/static/f1c1ec6871ccc541e473d90056b04233/913fc/stargazer_statistics.png 1170w","/static/f1c1ec6871ccc541e473d90056b04233/efb0c/stargazer_statistics.png 1755w","/static/f1c1ec6871ccc541e473d90056b04233/79b58/stargazer_statistics.png 2340w","/static/f1c1ec6871ccc541e473d90056b04233/6b6b8/stargazer_statistics.png 2424w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"On the extension-level side we have two libraries that are built around Swoole: "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/swooletw/laravel-swoole"},"children":[{"type":"text","value":"swooletw/laravel-swoole"}]},{"type":"text","value":" and "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/hhxsv5/laravel-s"},"children":[{"type":"text","value":"hhsxv5/laravel-s"}]},{"type":"text","value":". They are both established packages with a wide contributor base, but my colleagues and I ultimately concluded that the sandbox and safety features within laravel-swoole were a bit more mature and reliable in our testing."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"There are a few blog posts out there on getting started with laravel-s but not as many for laravel-swoole, so I felt particularly motivated to write and publish this article. The laravel-swoole default WebSocket implementation is built with Socket.io in mind, where laravel-s is a bit more agnostic in its implementation. Both packages have some feature overlap but some differences as well. I would consider both of them for your project and evaluate the best fit!"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h1","properties":{},"children":[{"type":"text","value":"Getting started with swooletw/laravel-swoole"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"First, let’s install either Swoole or OpenSwoole. I’ll be using Swoole for this example. Be sure to enable support for WebSockets on installation when prompted like so:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/34239a1147eeae2a84d2a91d9dee6f08/234d6/enable_sockets_support.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 69.24019607843137%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbElEQVQ4y5WTy07bQBSGj28TJzVJPPFl7PElCSKIl4AH6bZdURZdIBA0gbBqFrxJX6GLIvUBiNRuu6/UykKoLcT2qc5EUFouUi39OiMf65/vPzMGxAXg9Vf+E7+kP35dS0T8S1dXV/Li4kJeXl7e691RgogdRAR62OGrl+/H09eLwzfj7ycnJ8V4PC6Ojo6K4+Pj4uDgoNjZ2Sm2t7eLvb29Yjqdqj71JpNJsbu7+21/f7+eTCbvlKHzrOl2Of/kdjm22210XRebzaaqJM/z1PuVlRXsdruqBwD3lOf5RwAwiJDneX6e5xkmSbLo9/tVHMfVcDisRmujKk3TanV1VVXOeWXbdqVp2q10XV+Yponr6+sfAMAE0zRdKeU8jmMMw7AMggCFEJhlGdK61+thmqbY6XQeJAOAkupobe1MEVqWxX3fn5MJ57ykiL7vq7itVgsdx1FxaU2ybVtVit5oNKiWtNnGxsbSUNd1HkXRPEkSlFKW/X6fSJGIiXI4HCrCmzURk6lhGKjrOqlkjFHvD2Ecx4owCIIyiiJ1ELGUmKUpilAo8xtqMvs3Mhlvbm4uDZnFFCEZkSHNjQjzPMcwjrDLuaLjnD86Q6Ld2to6uzllN8uyORlQ5CRJaill7YuwTtudOmt3arvVqpnFagB4SAtN03AwGCwNDcPgQohzIvN9fyGEqDzPqyzGKhNACZ7WNRneXhsiTNP0M82QRIdD87JMcxlJ01DXNNQeEX1DkUej0e3FtsMwfOF53inn/G0QBLNerzdzHGfGGJs1Gg1Vn9Bb27ZPhRDPlWEYhmSqA4BF//V/yrpTtcFgAL8BHNRHajY+CAcAAAAASUVORK5CYII='); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Enable Sockets Support","title":"Screenshot of prompt to enable sockets support when installing Swoole in terminal","src":"/static/34239a1147eeae2a84d2a91d9dee6f08/913fc/enable_sockets_support.png","srcSet":["/static/34239a1147eeae2a84d2a91d9dee6f08/bc34b/enable_sockets_support.png 293w","/static/34239a1147eeae2a84d2a91d9dee6f08/da9f0/enable_sockets_support.png 585w","/static/34239a1147eeae2a84d2a91d9dee6f08/913fc/enable_sockets_support.png 1170w","/static/34239a1147eeae2a84d2a91d9dee6f08/234d6/enable_sockets_support.png 1632w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"text"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-text"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"pecl install swoole"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you run into any issues during the Swoole build process I included a few troubleshooting steps in the "},{"type":"element","tagName":"a","properties":{"href":"#appendix"},"children":[{"type":"text","value":"appendix"}]},{"type":"text","value":"."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now, create a new Laravel app. Continue bootstrapping the app using "},{"type":"element","tagName":"a","properties":{"href":"https://jetstream.laravel.com/2.x"},"children":[{"type":"text","value":"Laravel Jetstream"}]},{"type":"text","value":". I used the Intertia template, but I believe the API feature should work with Livewire as well."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"laravel new\ncomposer require laravel/jetstream\nphp artisan jetstream:install inertia"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Let’s set up that feature: just enable it by uncommenting the line in "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"config/jetstream.php"}]},{"type":"text","value":":"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"php"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'features'"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"["}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"text","value":"\n    Features"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"api"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"]"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Next, require the laravel-swoole package and publish its config files:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"composer require swooletw/laravel-swoole\nphp artisan vendor:publish --tag"},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"text","value":"laravel-swoole"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Here we’ll also want to adjust the config to enable the WebSocket server by default."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"php"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'websocket'"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"["}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'enabled'"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"env"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'SWOOLE_HTTP_WEBSOCKET'"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","boolean","constant"]},"children":[{"type":"text","value":"true"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"]"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now, for this basic example we’ll just have our handlers defined inline as closures in the websockets “routes” definition. For anything that is more than a simple exploration I would recommend creating controllers or "},{"type":"element","tagName":"a","properties":{"href":"https://laravelactions.com/"},"children":[{"type":"text","value":"Laravel Actions"}]},{"type":"text","value":" to store the handler code."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"php"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-php"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","php","language-php"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","delimiter","important"]},"children":[{"type":"text","value":"<?php"}]},{"type":"text","value":"\n\n"},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"declare"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"text","value":"strict_types"},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"element","tagName":"span","properties":{"className":["token","number"]},"children":[{"type":"text","value":"1"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n\n"},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"use"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","package"]},"children":[{"type":"text","value":"App"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Models"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"User"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"use"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","package"]},"children":[{"type":"text","value":"Laravel"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Sanctum"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"PersonalAccessToken"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"use"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","package"]},"children":[{"type":"text","value":"SwooleTW"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Http"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Facades"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n\nWebsocket"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"on"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'whoami'"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"function"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"text","value":"SwooleTW\\"},{"type":"element","tagName":"span","properties":{"className":["token","package"]},"children":[{"type":"text","value":"Http"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"{"}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"emit"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"text","value":"\n        "},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'message'"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":"\n        "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"getUserId"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"==="}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","constant"]},"children":[{"type":"text","value":"null"}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"?"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'You are not authenticated'"}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","double-quoted-string","string"]},"children":[{"type":"text","value":"\"Your userID is "},{"type":"element","tagName":"span","properties":{"className":["token","interpolation"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"{"}]},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"getUserId"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"}"}]}]},{"type":"text","value":"\""}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"}"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n\nWebsocket"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"on"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'login'"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"function"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"text","value":"SwooleTW\\"},{"type":"element","tagName":"span","properties":{"className":["token","package"]},"children":[{"type":"text","value":"Http"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"\\"}]},{"type":"text","value":"Websocket"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":" mixed "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$data"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"{"}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"if"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"is_string"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$data"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"{"}]},{"type":"text","value":"\n        "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"text","value":" PersonalAccessToken"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":":"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"findToken"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$data"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n\n        "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"if"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"instanceof"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","class-name"]},"children":[{"type":"text","value":"PersonalAccessToken"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"&&"}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","property"]},"children":[{"type":"text","value":"tokenable"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":"instanceof"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","class-name"]},"children":[{"type":"text","value":"User"}]},{"type":"text","value":"\n        "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"{"}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"loginUsing"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","property"]},"children":[{"type":"text","value":"tokenable"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n            "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$websocket"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"-"}]},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":">"}]},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"emit"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"("}]},{"type":"element","tagName":"span","properties":{"className":["token","single-quoted-string","string"]},"children":[{"type":"text","value":"'message'"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","variable"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]},{"type":"text","value":"\n        "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"}"}]},{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"}"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"}"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":")"}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":";"}]}]}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now we can test it out! Let’s start the Swoole server and test things out."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Note: You will need to disable Xdebug before you can run any Swoole code. If you skip this step your terminal will be overwhelmed with warning logs when you run this next command and things might not behave properly. If you expect to be doing a lot of Swoole development then you may want to configure a tool or script to enable or disable loading the Xdebug extension. For macOS there’s an open source tool to add a toggle button in the menu bar called "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/deligoez/xDebug-Toggler"},"children":[{"type":"text","value":"xDebugToggler"}]},{"type":"text","value":"."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"php artisan swoole:http start"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Try navigating to "},{"type":"element","tagName":"a","properties":{"href":"http://127.0.0.1:1215"},"children":[{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"http://127.0.0.1:1215"}]}]},{"type":"text","value":" and seeing if you are able to register and login just like if you were to run "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"php artisan serve"}]},{"type":"text","value":". From there, head to the API Tokens section:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/dadfb062a5e2d828215d2be8e224afda/a40cf/jetstream_api_tokens.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.84584342211461%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC2ElEQVQ4y5WS327TSBTGJ8kmrWhJS/mjrlQt2qqiEhJS+wp7gQJ3RrwBl0T7MsteobY0C4Ir+gfERW/oIvYNYEWSuiF1qu0m8dgzsceOPTMfGrugctELLP30feeMdXzO8ZBfqnOX5q9duDdZKVo3l29YKyur1vLysrW0tGRNT09b5XLZqlQq3zAxIcQqFotWgZDMG34qle5PTk4ukJ8vX7195fJFXJqp4tfr17G4uIiFhQXMz89jYmICxWIRpVIpw/hyuYxqtYqpqWlUZ69idnYGc3NzuDhTNfmH5PHjR3fevmvhU9OWHz58lK1WS7XbbWXU0Gy2zvhmpp1OR9n2oep0HWXbtjw4OEha7RbW1tbq5M2b17VQpAjCsQKgfxSdqwSAvb29Onn+/EXN8xnCcKSSNNbxOEUcjxEZohhhGOWI6Hs/ChAMXIRRlBUUQmB7e6dO1jcatfE4Qn+o1Wcn0oP+CQZDF57ng3MOqRSkVEhT+R0mF4/HCIXQSkk5CkJsmYJr65u1JIlBPa26TqD7/f/guhSe52UFkySBUmYb5z5Zh2EosLW1XScbG5s1M5pMx4rSgWaMgzEG13VBKcXJyQl8xiCl6SxFkqSZnvF5QRHlBdc3NmtxHJsxlEuZpp4P6rFvMB6A8RHMng2U5ufGu9SH73OttZZCRHj5cqtOnjxp1EIhkEqphIh0FMf5T4nirAMzrsr2KLN9KqXznNZffdZhEArs7OzWSaPxtJZ/ncv+kCrqMRWKSAWhQahRILKY8UANXU/5jGexyZ85T/kowP7+3w9J469nd80VCaMYA9cDzYuDnuJ6LLtCUmm4lOP/gY8hNXkOj5lVjCBOr9n79//8Tv549Odvnc9dcWAfsY//HrJPzS4/7PS40zvmjmO0x4+cHNt2eLvtZHp0euY4PdY7PvYPO910d/fVA0IImSKE3CKksEpIYYUQci7lcmGlMlHI9Jx3Zr8A2ixLZNOtMRMAAAAASUVORK5CYII='); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Jetstream API Tokens","title":"Screenshot of Laravel Jetstream landing page with the navigation menu opened and the API Tokens nav item highlighted.","src":"/static/dadfb062a5e2d828215d2be8e224afda/913fc/jetstream_api_tokens.png","srcSet":["/static/dadfb062a5e2d828215d2be8e224afda/bc34b/jetstream_api_tokens.png 293w","/static/dadfb062a5e2d828215d2be8e224afda/da9f0/jetstream_api_tokens.png 585w","/static/dadfb062a5e2d828215d2be8e224afda/913fc/jetstream_api_tokens.png 1170w","/static/dadfb062a5e2d828215d2be8e224afda/efb0c/jetstream_api_tokens.png 1755w","/static/dadfb062a5e2d828215d2be8e224afda/79b58/jetstream_api_tokens.png 2340w","/static/dadfb062a5e2d828215d2be8e224afda/a40cf/jetstream_api_tokens.png 2478w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Create a token and save it for the next step."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/0d4797c19927810f9c1900df3b07b583/a40cf/create_api_token.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.84584342211461%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACb0lEQVQ4y6XRTU8TQRgH8IctW6CVLVAxkBRNiNh4MIGv4MFUb2v8Bh5p/DQmxHAyQouJIfH1JJRiJB5ENBqxLd0FYwnGvuz77uzuzGNmKdV4IFE3+eWZmWT+z8wsnJfGRifOJW4OxgX5cnZGnp2dk7PZrHxx5pKclFJyPB6XBwYGosqJoigDgCwIgtwHIENfnwwxUe7vF28NDQ1mYDI9fu1sehhHUxJemJrC6elpzGQyODExiYlEAgVBwFgsFuFjURRRkiRMJs+gNDKOqVQKx9JpHE5JfH0eFhfvXi+VK1ipKuHul92wWq3SWq3WU6lUKF/jTsaqqtJ6vU6V/a9UUZSwtrfnV2s1XF1dzcOL589ythMiIQFFRPaPQkTEnZ2dPBSLKzlN01DTDWrZDnNcDx2HcyMun7tub/47+xjfE1qWg+XyZh6KKw9zjcPvqB406LfGEfvR6mCzpWGrraPteBFCAgwC2uN3a0gpUkoZYyz0PILrpXIelgvFHA9otjXabGus2eqgpluoGxbaDsF2R0fTcjAMEYOusIvxy3avTIiPpY1yHh4sLec03UTLdqlpOcyy3SiAcz2Ctu0e72K/6LrPT4bdLwpkjOHa+kYelpYKUaBpOdQwbWaYdhTGq0d89AiJNvcwFjUKgiBKIsRnHz5+DhuHR7i19eb0QP7ojkt6b3kiOnlUfWx1NHa/8Ch89/4Tvt3ePr5yRzN4QGiYNv2Tblin4gdBxIA3frlWmodCceUGfzePBFHHf2E5Hv8HuPnq9R1YWLh3da+uOop6oCvqPmf8JV1RD7S6sh88fvL0NgBAEgCuAMAcAMz+p5GfZ05M9YjDnygAAAAASUVORK5CYII='); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Create API Token","title":"Screenshot of Jetstream page for creating an API token","src":"/static/0d4797c19927810f9c1900df3b07b583/913fc/create_api_token.png","srcSet":["/static/0d4797c19927810f9c1900df3b07b583/bc34b/create_api_token.png 293w","/static/0d4797c19927810f9c1900df3b07b583/da9f0/create_api_token.png 585w","/static/0d4797c19927810f9c1900df3b07b583/913fc/create_api_token.png 1170w","/static/0d4797c19927810f9c1900df3b07b583/efb0c/create_api_token.png 1755w","/static/0d4797c19927810f9c1900df3b07b583/79b58/create_api_token.png 2340w","/static/0d4797c19927810f9c1900df3b07b583/a40cf/create_api_token.png 2478w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Now, let’s connect to the server via WebSocket and test our handlers! Open up your WebSocket client of choice. I’ll be using "},{"type":"element","tagName":"a","properties":{"href":"https://www.postman.com/"},"children":[{"type":"text","value":"Postman"}]},{"type":"text","value":" here."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Let’s get connected and make sure everything works. Enter your hostname and port (likely "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"127.0.0.1:1215"}]},{"type":"text","value":") and hit connect. You should see two messages come through and the status should still read "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"CONNECTED"}]},{"type":"text","value":"."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/5d2d1fe17ad91fc6d18b15ed17a4d861/d2512/websocket-connect.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACOklEQVQ4y32SS2/TQBDHLVqUOLmD1MTe3Zl9eWMnTpoKtRKCW8SRG5/AKRKfmlbixAegtT1oNjGlVdvDT/uYnf+8NtkUHjYr/aMKrtmslvtQFPuqqvZlWUastZH1er3fbrfRprWOd965/eery31d13x/jYjvkmD0dV15Wi+XtK1XFEJBRVGQc46stQQAET5778kYQ0opQsR4rpfLaGMfANglqLImNzVlxeUfZ027WJRtuVi01tpWStkqpSK8H86I2BpjWudcG0K4997faa1JSrlLtNYNZ+K8v9dG94jYA8CrKKUieZ73IYT+4uKiY408z3cJADScblVVrbam50hcIpf1HFyydZ6KItC6rrktLN6xX5ZlB0HuByC2SqpeSvmiGMPBsrP3tAiBzs/PiTM0xnSokWZns10ihIiCqDX3qB8cB+GnAaQU9Onrnsr1B8rnM8pB9gJkB0rRfD7fJfMsa7x3HJEb3g9THUrnEnll4QOCys0VoXGkhCARsM8L6EABzVhQiqyRYAi0b7XG2EOGhf5fYxVH8mwWheP3UdAb1B0HjRnK/CBo/KJdVmW/WvFfDPEPxuk79+8PMrxXCiKcsTGm994/CIpcNMdm3yFix/DUpJQvwvYn3D9kiOq7etSj16c8DOopR8EvibL4TVv9GwB+IuItANwqpW6FEM/CNn7Db4/cAMANIv7Ksuxj8jZ5M52MUz2ZTMx0OtVpmprRaGROTk7M6enpI/huPB6b49thjb5pmsJoNBr/BWn9Vi0wXoNpAAAAAElFTkSuQmCC'); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Connect to WebSocket with Postman","title":"Screenshot of Postman configured with the hostname and port of the local example WebSocket server","src":"/static/5d2d1fe17ad91fc6d18b15ed17a4d861/913fc/websocket-connect.png","srcSet":["/static/5d2d1fe17ad91fc6d18b15ed17a4d861/bc34b/websocket-connect.png 293w","/static/5d2d1fe17ad91fc6d18b15ed17a4d861/da9f0/websocket-connect.png 585w","/static/5d2d1fe17ad91fc6d18b15ed17a4d861/913fc/websocket-connect.png 1170w","/static/5d2d1fe17ad91fc6d18b15ed17a4d861/efb0c/websocket-connect.png 1755w","/static/5d2d1fe17ad91fc6d18b15ed17a4d861/79b58/websocket-connect.png 2340w","/static/5d2d1fe17ad91fc6d18b15ed17a4d861/d2512/websocket-connect.png 2484w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you’re wondering what those 1-2 digit numbers prefixing the server messages are, those are "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/socketio/socket.io-protocol#packet-encoding"},"children":[{"type":"text","value":"Socket.io packet type encodings"}]},{"type":"text","value":". You can remove these and adjust anything about frame parsing by creating your own implementation of "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"\\SwooleTW\\Http\\Websocket\\Parser"}]},{"type":"text","value":"."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Let’s send a "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"whoami"}]},{"type":"text","value":" payload using this JSON:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"json"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-json"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-json"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"["}]},{"type":"text","value":"\n  "},{"type":"element","tagName":"span","properties":{"className":["token","string"]},"children":[{"type":"text","value":"\"whoami\""}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"]"}]}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/68d7813ef9dd52c4ff2b9ee969eb463e/d2512/whoami_no_auth.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACSElEQVQ4y4VS227UMBCNaNHe1FeE2o3t8fgSO8neuqq0SEjwtOIP+ACULRJfTSvxxAfQJhk0prvdtiAejsZz8fGcGWerUOjV3Hyro29W89kuhrCr63pXVVWCcy5huVzu1ut1yhljUqzwfvfh3Wa3WCw4fo2Ib7JozfWiLmg5m9F6MacYA4UQyHtPzjnSWiewXxQFWWsJAAgRk7+YzVKO72ittxlC3gi7oDxsfnln27Ks2qosW+dcq5RqASCBz3sfEVtrbeu9b2OM9977O2MMKaW2mTGm4U6KItwba3pE7LXW/wUA9FLKPsbYX11dddyllHKbaa0bbjeG0DIhv8QSWdZzsEzOo3XkfUF1XXOsF0J0nDsQsuOcbRGgV0r9lewJsXhL81lFq8tLKmPZO2s7NEjn5+fbTAjRIBoeNs+nf375xQNKkvj4hdx8Q6gEgcUenelQI11cXGyzaZ43vK2yjDz0RMiS99J5q6zgQKwUyWpDCj2BlCSC7vMCOg2aptPpNpNSNKCRjPNtGWO//x4xxgPZi5mKi9SpYvkae4um45o/hEI0WiM57/kbHCTvSbiz5wB4zPEivfePhEpMG6ktoYt3oSi6sqy6EAJ/g845l2xRFAewDwBMkKwxhuvuHwk1fAWDZJ1LMo+l/gvHC2Of6x8IP2Xg8LOx5qcG+A5a3yqlEoQQt1LKZI8BALeIeIwbrfUNIv7I8/x99jp7NRkPR2Yymdizs7NkB4OBPTk5saenp0/AseFwaMfjseW6B5vujEYjPRgMhr8BkIFW1gyrwQcAAAAASUVORK5CYII='); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"WebSocket whoami test","title":"Screenshot of Postman client after sending a whoami WebSocket message, with the server returning an unauthenticated message response","src":"/static/68d7813ef9dd52c4ff2b9ee969eb463e/913fc/whoami_no_auth.png","srcSet":["/static/68d7813ef9dd52c4ff2b9ee969eb463e/bc34b/whoami_no_auth.png 293w","/static/68d7813ef9dd52c4ff2b9ee969eb463e/da9f0/whoami_no_auth.png 585w","/static/68d7813ef9dd52c4ff2b9ee969eb463e/913fc/whoami_no_auth.png 1170w","/static/68d7813ef9dd52c4ff2b9ee969eb463e/efb0c/whoami_no_auth.png 1755w","/static/68d7813ef9dd52c4ff2b9ee969eb463e/79b58/whoami_no_auth.png 2340w","/static/68d7813ef9dd52c4ff2b9ee969eb463e/d2512/whoami_no_auth.png 2484w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"As expected we get the unauthenticated message. So let’s log in! Refer to the following JSON payload:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"json"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-json"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-json"]},"children":[{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"["}]},{"type":"text","value":"\n  "},{"type":"element","tagName":"span","properties":{"className":["token","string"]},"children":[{"type":"text","value":"\"login\""}]},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":","}]},{"type":"text","value":"\n  "},{"type":"element","tagName":"span","properties":{"className":["token","string"]},"children":[{"type":"text","value":"\"YOUR_API_TOKEN\""}]},{"type":"text","value":"\n"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"]"}]}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/1261d6f17d687f82a88fc097364c84a9/d2512/login.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACWklEQVQ4y4WS227TQBCGXbWSm+QeiSb2HmZ29hA7daKqoggkxE3EG/ACOEXiqaESVzwA1PagWUjVokpcfBrPjvzvzD9b7GKwu0v80ibf7y43hxTjoW3bQ9M0GSLKbLfbw9XVVa4hYj4L3h/evb45dF0n57cA8KJIDm+7NvB2s+Gr7pJTihxjZO89ExFbazOShxDYOcfGGAaAnHebTa7JP9bafQGm6mvXcRVufnpyw3rdDM16PRDRoLUejDEZ+T7mADA45wbv/ZBSuvfe/3LkWWu9LxCxl05CjPfocAKAyVr7X4wxk1JqSilN19fXow+R66raFxagjzFI2wOIIGIeSRuT42NkTERkQGIfArdtK2dTXdcjOuIqC1rbZz88DdHhRAjsHWbA2ueF65fcpMgb8Y9oMtqMgI6Xy+W+qOu6l1vFM7B2EhHIizBspVOtnwpqxfX7Txx3b5jQsgt+ouhH8fBiebEvVlXVy7ZSSmL6pB+NJ1Euky2LcEYprtevWAGxUYpVgklFOwI4Xq6W+0KpujcWGJ1sLE5/xR9omiY/C0GejNQJNHtyLH6jhQktZA9Xq9W+UPUfQfJhaJpmEoGu6zJiughIt88hExDRRER55CwoHspIRPQrhDBaa0djTI5a61EpleNjpH7EOTci4v2DoLHqs0ZipCSbfjKeIG/0X44LEk+PZ+iy4IfCkP0IRD8A3Vfv6c57f+ecuwOAJyBijsfaMUfEbwIgfq+q6m1xVpwt5mWJs1npyrLE09NTd3Jy4oqieBapz2Yzt1gs3Hw+l2+U/Py8tGVZnv8GQ1BYPV7HwjwAAAAASUVORK5CYII='); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"WebSocket login test","title":"Screenshot of Postman client after sending a login WebSocket message, with the server returning additional token information","src":"/static/1261d6f17d687f82a88fc097364c84a9/913fc/login.png","srcSet":["/static/1261d6f17d687f82a88fc097364c84a9/bc34b/login.png 293w","/static/1261d6f17d687f82a88fc097364c84a9/da9f0/login.png 585w","/static/1261d6f17d687f82a88fc097364c84a9/913fc/login.png 1170w","/static/1261d6f17d687f82a88fc097364c84a9/efb0c/login.png 1755w","/static/1261d6f17d687f82a88fc097364c84a9/79b58/login.png 2340w","/static/1261d6f17d687f82a88fc097364c84a9/d2512/login.png 2484w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"And we get the "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"$tokenInstance"}]},{"type":"text","value":" we emitted in our "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"routes/websocket.php"}]},{"type":"text","value":" handler for "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"login"}]},{"type":"text","value":", and the default parser was nice enough to JSON-encode it for us! We can send another "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"whoami"}]},{"type":"text","value":" to check our work:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/bd66dadb36c7f5737067d4ddf7db0884/d2512/whoami_with_auth.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 68.92109500805154%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAABYlAAAWJQFJUiTwAAACXklEQVQ4y4VT227bMAw11naOk/dhaGNZEkXJli+5NCiQAQO2p2B/sA8Ykg7YV28B9rQPWGuLAxU4Q9cCezigSVqHPKSUrKtSrxfmW+vdfr3oDr6qDm3bHpqmibDWRqxWq8Nms4k5Y0yMlc4dPrzbHpbLJcfvAeBN4tHcL9uSVl1Hm+WCvK+oqipyzpG1lrTWEeyXZUmISEopAoDoL7su5viM1nqXgMr3ApeUV9vfzmJf103f1HVvre2llL1SKoK/Rx8AekTsnXO99/7ROfdgjCEp5S4xxuy5k7KsHg2aAABBa/1fKKVCURTBex/u7u4G5hBC7BKt9Z7b9VXVMyFXYoks61+wTM4DWnKupLZtORaEEAPn8jw/EbJjLfagVJBSvkj2hFi8pUXX0Pr2lmpfB4s4cKHr6+tdIoTYAxgeNs8njIdG4mcFZEHi4xeyiy2BFKQQAlgzgAa6ubnZJfM83/O26rrmoUdClhylAcStsv1LKKlotiTBkSoKEpUOolSDVorm8/kuKQqxVxrIWN5YFfgKMJhwnNvY6Rn5NcmiICkVGQ0BNMQZngjFidC5sm+aJnBHvDEmZLA/xtiO95MRFVgb0No4wzMhS0RrH7z3Q13XQ9d1EW3bDhwzxjwDInJX0SLi45lQavlVG4iVm6Y5vwjv/fmlvLTpUf7YrTrN8FMiLXxGC7/Q6O+I9qi1Pkopj3meH4UQx6IonoDzAHA0xoz2BwMAfuZ5/j55nbyaTdKpSbMpZpOJmU6nOJlM8OrqCi8vL/Hi4uIJ0jTFLMtwNpsh/5tlmWE/yzKdpunkD4I+V6B1bewiAAAAAElFTkSuQmCC'); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"WebSocket whoami second test","title":"Screenshot of Postman client after sending a whoami WebSocket message, with the server returning the authenticated user's id","src":"/static/bd66dadb36c7f5737067d4ddf7db0884/913fc/whoami_with_auth.png","srcSet":["/static/bd66dadb36c7f5737067d4ddf7db0884/bc34b/whoami_with_auth.png 293w","/static/bd66dadb36c7f5737067d4ddf7db0884/da9f0/whoami_with_auth.png 585w","/static/bd66dadb36c7f5737067d4ddf7db0884/913fc/whoami_with_auth.png 1170w","/static/bd66dadb36c7f5737067d4ddf7db0884/efb0c/whoami_with_auth.png 1755w","/static/bd66dadb36c7f5737067d4ddf7db0884/79b58/whoami_with_auth.png 2340w","/static/bd66dadb36c7f5737067d4ddf7db0884/d2512/whoami_with_auth.png 2484w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"We now have a WebSocket server and a way to authenticate connections! This example is very basic, but I hope it gives a background to build something great with."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h1","properties":{"id":"appendix"},"children":[{"type":"text","value":"Appendix"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h2","properties":{},"children":[{"type":"text","value":"Notes on using Swoole and laravel-swoole"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Try Using Swoole (or RoadRunner) to serve your app"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"element","tagName":"a","properties":{"className":["gatsby-resp-image-link"],"href":"/static/4dddb8323d9710d1a92cea6578e78a2c/0b97c/swoole_http_benchmark.png","style":"display: block","target":"_blank","rel":["noopener"]},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-wrapper"],"style":"position: relative; display: block; margin-left: auto; margin-right: auto;  max-width: 1170px;"},"children":[{"type":"text","value":"\n    "},{"type":"element","tagName":"span","properties":{"className":["gatsby-resp-image-background-image"],"style":"padding-bottom: 51.40728476821192%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAsTAAALEwEAmpwYAAACK0lEQVQoz11QTW/TQBCdjR3v2mt7vf6Is0naUJyK1kU9VOqlEscWxIWfwD+o1ENz6KWnFIGSuBI3ZMQf4wxF3KCnJs6g3RLUcnh6M5o3b98OvH39Rlbvxx+n8w9fZtNZPZ/P6tlsVk+n0/r6+rqeTCb15eVlfTW5qt9NrupqXj1GVWnd56qqPo3H4wKigO/mw+eYP3mK6aCPeX8TB12FvX4P0zRFz/Vwc2sT+3tbKAuFsuhhkueYZTEmnQTTJMU4jjHLMjw4OHgFnHs7Xqh+hYKvhKB3QcAWjtNeOI6zAACDNEsXFrPu+xYsgDzge82SUro8PDw8hiiSO4zZt1mWogjFCgD+gRBiOI7jFQHyaPYfMAxDPD8/PwEp5a4Q0a3+nhBiRSlF13WRMWZY90opTNIEfd/HIAgMtMFfXnHOcTAY4Onp6QmEkdhVPXWrl/I8X3W7XXMPbahh2zaqnsLt7W0cDocGGxsb2Ol0jKk21A/pQPv7+ydg2/aOEOK3NpFSLpMkaYIgaHzfbwghDQA0Usomz/MmiqImjuNGazjnDWNMz5daQyltjo6OjiEIg1IpdSelxCiKzDc8zzPpHMdBfZ+yLLEoCnMCnUqfQScnhJi5ht6/uLh4qQ0L5rKvVqv107bt74SQGwC40dxqtUw9Go1uwjA09Rp6/kDzQ0r57ezs7AUAQNu2rJHrunuU0tLzvJJzbnhdW5ZVttvt0vd90695rfE8T+8+AwD2B6Ih3fwCh/e9AAAAAElFTkSuQmCC'); background-size: cover; display: block;"},"children":[]},{"type":"text","value":"\n    "},{"type":"element","tagName":"img","properties":{"className":["gatsby-resp-image-image"],"style":"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;","alt":"Swoole benchmark comparison","title":"Screenshot of wrk benchmarks against artisan serve and Swoole, showing 36.55 requests per second for artisan serve and 218.66 for Swoole","src":"/static/4dddb8323d9710d1a92cea6578e78a2c/913fc/swoole_http_benchmark.png","srcSet":["/static/4dddb8323d9710d1a92cea6578e78a2c/bc34b/swoole_http_benchmark.png 293w","/static/4dddb8323d9710d1a92cea6578e78a2c/da9f0/swoole_http_benchmark.png 585w","/static/4dddb8323d9710d1a92cea6578e78a2c/913fc/swoole_http_benchmark.png 1170w","/static/4dddb8323d9710d1a92cea6578e78a2c/efb0c/swoole_http_benchmark.png 1755w","/static/4dddb8323d9710d1a92cea6578e78a2c/79b58/swoole_http_benchmark.png 2340w","/static/4dddb8323d9710d1a92cea6578e78a2c/0b97c/swoole_http_benchmark.png 2416w"],"sizes":["(max-width:","1170px)","100vw,","1170px"]},"children":[]},{"type":"text","value":"\n  "}]},{"type":"text","value":"\n  "}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you end up using a Swoole-based solution for your WebSocket server, why not serve HTTP requests using Swoole as well? Laravel’s request lifecycle by default is actually pretty redundant: for each incoming request that your Laravel app handles it needs to be fully bootstrapped every time: framework, service providers and all. There is a first-party solution to this: "},{"type":"element","tagName":"a","properties":{"href":"https://laravel.com/docs/9.x/octane"},"children":[{"type":"text","value":"Laravel Octane"}]},{"type":"text","value":", which uses your choice of Swoole (or OpenSwoole) or RoadRunner to provide a relatively easy way to serve many more requests by booting and loading the application into memory. Laravel Octane with RoadRunner is very likely to be a drop-in replacement for Nginx/PHP FPM for your Laravel app if you’d rather not add Swoole to your stack. Either way, it could be an easy win to dramatically increase your throughput for typical HTTP server needs using Laravel Octane, laravel-swoole, laravel-s, or another similar solution."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Choosing a WebSocket Room Driver"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Be sure to choose your room driver, which associates WebSocket connections with users as well as membership in channels, appropriately for your use case. By default, the library will use a Swoole table to store these records. Swoole tables are an incredibly performant data store compared to Redis, as "},{"type":"element","tagName":"a","properties":{"href":"https://cerwyn.medium.com/whos-the-winner-redis-swoole-table-or-local-cache-ec315487db04"},"children":[{"type":"text","value":"Cerwyn Cahyono concluded"}]},{"type":"text","value":" in May 2021. One consideration however is horizontal scaling: if you have more than one WebSocket server behind a load balancer, for instance, you need to consider that Swoole tables are only accessible by the Swoole server process and its forks."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you need to have a common record among all of your WebSocket server instances with laravel-swoole then you may want to consider the provided Redis driver ("},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"\\SwooleTW\\Http\\Websocket\\Rooms\\RedisRoom"}]},{"type":"text","value":"), or creating your own implementation. Be sure to add a prefix unique to the server for all records, that way you don’t end up sending a message intended for one WebSocket connection on Server A to another, unrelated WebSocket connection on Server B."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Implementing middleware for WebSocket frames/messages"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"You may also find that you’d like to have middleware for all incoming WebSocket messages. If all you need to do is interact with parts of the Frame then you can add this handling in a custom handler or parser. These are defined in "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"config/swoole_websocket.php"}]},{"type":"text","value":". If you need to get the user ID and interact with a WebSocket instead then you’ll have to override and extend the "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"\\SwooleTW\\Http\\Concerns\\InteractsWithWebsocket"}]},{"type":"text","value":" trait to directly modify the Swoole "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"onMessage"}]},{"type":"text","value":" handler."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Adding Swoole to an existing stack/system"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you have a system where a Swoole WebSocket server stands alongside other pieces like an HTTP server, queue worker, etc. then you will need to implement some kind of global pub/sub infrastructure that coordinates between the Swoole WebSocket server and everything else. Redis is one way to fill that need. You could have an async Redis client boot with the Swoole server and "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"SUBSCRIBE"}]},{"type":"text","value":" to a given channel. The client could listen for a JSON payload which could simply have a user ID and data to emit to the authenticated WebSocket connection. That way, you could issue WebSocket messages from non-Swoole contexts by simply "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"PUBLISH"}]},{"type":"text","value":"ing the JSON payload to the corresponding channel. This does have the added overhead of having to establish a Redis connection for each emit from a non-Swoole context, but you get the flexibility of cooperating well with your existing system."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Troubleshooting installation of Swoole extension"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"When installing the Swoole extension on both ARM64 and x64 macOS machines I’ve run into a few issues and wanted to share how I resolved them."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h4","properties":{},"children":[{"type":"text","value":"fatal error: ‘pcre2.h’ file not found"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"If you get this error when installing Swoole then you need to be sure you have "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"pcre2"}]},{"type":"text","value":" installed. On macOS, you can do this using Brew:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"brew "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"install"}]},{"type":"text","value":" pcre2"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Then you can use the Brew CLI to grep the location of the "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"pcre2.h"}]},{"type":"text","value":" file and link it under your current PHP version’s "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"include/php/ext/pcre/"}]},{"type":"text","value":" directory."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"brew list pcre2 "},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"|"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"grep"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","string"]},"children":[{"type":"text","value":"'pcre2\\.h$'"}]},{"type":"text","value":"\n\n"},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"ln"}]},{"type":"text","value":" -s /opt/homebrew/Cellar/pcre2/xx.yy/include/pcre2.h /opt/homebrew/Cellar/php@x.y/x.y.z/include/php/ext/pcre/pcre2.h"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h4","properties":{},"children":[{"type":"text","value":"fatal error: ‘openssl/ssl.h’ file not found"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This guide doesn’t require building Swoole with OpenSSL support, but if you do you may need to set your OpenSSL directory during the build config. You can do so by first ensuring that you have OpenSSL installed locally, on macOS you can also do this using Brew:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"brew "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"install"}]},{"type":"text","value":" openssl"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Then you can once again use the Brew CLI to get your OpenSSL directory and pass it in during the extension build configuration, right after executing "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"pecl install"}]},{"type":"text","value":". When prompted to enable OpenSSL support, type out “yes” and then add the "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"--with-openssl-dir"}]},{"type":"text","value":" flag inline like so:"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"div","properties":{"className":["gatsby-highlight"],"dataLanguage":"shell"},"children":[{"type":"element","tagName":"pre","properties":{"className":["language-shell"]},"children":[{"type":"element","tagName":"code","properties":{"className":["language-shell"]},"children":[{"type":"text","value":"brew --prefix openssl\npecl "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"install"}]},{"type":"text","value":" swoole\n\n\n"},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"enable"}]},{"type":"text","value":" openssl support? "},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"["}]},{"type":"text","value":"no"},{"type":"element","tagName":"span","properties":{"className":["token","punctuation"]},"children":[{"type":"text","value":"]"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","keyword"]},"children":[{"type":"text","value":":"}]},{"type":"text","value":" "},{"type":"element","tagName":"span","properties":{"className":["token","function"]},"children":[{"type":"text","value":"yes"}]},{"type":"text","value":" --with-openssl-dir"},{"type":"element","tagName":"span","properties":{"className":["token","operator"]},"children":[{"type":"text","value":"="}]},{"type":"text","value":"/opt/homebrew/opt/openssl@3"}]}]}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h2","properties":{},"children":[{"type":"text","value":"Notes on WebSockets"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Subprotocol gotcha"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Be sure to pay attention to the "},{"type":"element","tagName":"code","properties":{"className":["language-text"]},"children":[{"type":"text","value":"Sec-WebSocket-Protocol"}]},{"type":"text","value":" HTTP header on the initial connection request. If the client request specifies a protocol in the header and the server doesn’t respond with that protocol, or any, then some browsers like Chrome will just drop the connection, which technically follows the WebSocket spec more closely, and others like Firefox and Safari will connect without hesitation."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Which WebSocket dev client to use?"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For me this has been "},{"type":"element","tagName":"a","properties":{"href":"https://firecamp.io/"},"children":[{"type":"text","value":"Firecamp"}]},{"type":"text","value":", but I have gotten frustrated with the bugs and poor performance of the app. It has a lot of potential, so I’m definitely going to keep watching it! "},{"type":"element","tagName":"a","properties":{"href":"https://insomnia.rest/"},"children":[{"type":"text","value":"Insomnia"}]},{"type":"text","value":" just added WebSocket support, but it is still immature and lacking features. As of Jan 2023 I recommend using "},{"type":"element","tagName":"a","properties":{"href":"https://www.postman.com/"},"children":[{"type":"text","value":"Postman"}]},{"type":"text","value":", though note that even for them WebSockets support is somewhat of a beta."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Give your WebSocket server a heart(beat)"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"Be sure to implement some kind of heartbeat function for persistent WebSocket connections. It’s a good general practice, and helps when you have or want infrastructure configurations that close inactive connections. With many subprotocols the heartbeat is client-initiated."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"h3","properties":{},"children":[{"type":"text","value":"Authentication Patterns"}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"There are many patterns for authenticating WebSocket connections, but can be categorized as either during the initial connection request (authentication-on-connection) or afterwards within the established connection (authentication-after-connection). During the initial connection request the two most common approaches are to either use a cookie, or a token in the header or body. Typically, with the authentication-on-connection approach the connection is closed by the server if the authentication check fails. With the authentication-after-connection approach typically servers have a timeout which closes connections that don’t authenticate within a given timeout. At Knack, we created a WebSocket backend that implemented the "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md"},"children":[{"type":"text","value":"graphql-transport-ws"}]},{"type":"text","value":" subprotocol which frontend library "},{"type":"element","tagName":"a","properties":{"href":"https://github.com/enisdenjo/graphql-ws"},"children":[{"type":"text","value":"enisdenjo/graphql-ws"}]},{"type":"text","value":" supports."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"For reference, laravel-swoole is configured by default for authentication and other middleware to be run on the initial connection request. While this is a valid approach, my impression is that the dominant WebSocket authentication pattern is to support authentication-after-connection. For this guide I implemented an authentication-after-connection flow as a barebones custom subprotocol. Before proceeding with your project be sure to consider what kinds of clients will be connecting to your WebSocket server and choose your approach accordingly."}]},{"type":"text","value":"\n"},{"type":"element","tagName":"p","properties":{},"children":[{"type":"text","value":"This article has been "},{"type":"element","tagName":"a","properties":{"href":"https://heymisha.com/blog/laravel-websocket-server-guide/"},"children":[{"type":"text","value":"cross-posted on my personal blog"}]}]}],"data":{"quirksMode":false}},"excerpt":"WebSockets are a ubiquitous tool for building real-time experiences for users. They provide a persistent, bidirectional connection between a…","timeToRead":13,"frontmatter":{"title":"Setting up a WebSocket server for your Laravel app","userDate":"January 30 2023","date":"2023-01-30T15:00:00.0Z","tags":["Engineering"],"image":null,"author":{"id":"Misha Hawthorn","bio":"Senior Software Engineer","avatar":{"children":[{"__typename":"ImageSharp","fixed":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAQDAQX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAc9Zbprdwz4dpmgJ/8QAGxAAAgMAAwAAAAAAAAAAAAAAAQIAAxEQEiH/2gAIAQEAAQUCtbAB0EubFRtSWetXx//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/ASM//8QAFREBAQAAAAAAAAAAAAAAAAAAEAH/2gAIAQIBAT8BIf/EAB0QAAEDBQEAAAAAAAAAAAAAAAEAAlEQESAhQXH/2gAIAQEABj8CAnqu1xM0Hq0cf//EABwQAAICAwEBAAAAAAAAAAAAAAERACExQVEQYf/aAAgBAQABPyE4xh0Q1AgWiEPD46DMKbKDqAyg+zDOr+wbuf/aAAwDAQACAAMAAAAQvD++/8QAGBEAAgMAAAAAAAAAAAAAAAAAEBEBITH/2gAIAQMBAT8QRWMQP//EABcRAAMBAAAAAAAAAAAAAAAAAAABMRD/2gAIAQIBAT8Qu3T/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMUFRYf/aAAgBAQABPxBZ/XA9r7FpIcu+kIqzRRW4FlTkUwQK3HQ4wfKiRorRa/XYFchTWJ//2Q==","width":400,"height":400,"src":"/static/07417efdedf83537485b65256cece4bf/c32cc/misha-hawthorn.jpg","srcSet":"/static/07417efdedf83537485b65256cece4bf/c32cc/misha-hawthorn.jpg 1x"}}]}}}},"relatedPosts":{"totalCount":20,"edges":[{"node":{"id":"e7b92535-950b-59c7-95f5-8ab80af08338","timeToRead":5,"excerpt":"IntroClueless, but looking to up your game on accessibility. Starting on a new project and wanting to make sure you keep accessibility in…","frontmatter":{"title":"Beginners Tips for Web Accessibility in React"},"fields":{"slug":"/a11y/"}}},{"node":{"id":"6c69d8f0-7998-5387-93e4-6547e3940c0d","timeToRead":9,"excerpt":"Pull Requests (PRs) are an essential part of the software development workflow, allowing developers to propose changes, contribute new…","frontmatter":{"title":"The Art (and Science) of Reviewable PRs"},"fields":{"slug":"/art-and-science-of-reviewable-prs/"}}},{"node":{"id":"2ababe59-b3e5-5fc9-9863-6d51c8356a00","timeToRead":10,"excerpt":"Late last year, we started looking into embedded analytical dashboard solutions. We concluded that embedding pre-built dashboards would…","frontmatter":{"title":"Unlocking the Power of Data with Cube on AWS: A Comprehensive Guide"},"fields":{"slug":"/aws-cube-js-deployment/"}}}]}},"pageContext":{"isCreatedByStatefulCreatePages":false,"slug":"/laravel-websocket-server-guide/","prev":{"excerpt":"Make the most out of Laravel Factories: How we migrated from Factory MuffinWe use Laravel to power the Knack API that is consumed by our iOS…","timeToRead":3,"frontmatter":{"title":"Make the most out of Laravel Factories: How we migrated from Factory Muffin","tags":["Engineering"],"date":"2022-12-16T15:00:00.0Z","draft":false,"image":{"childImageSharp":{"fluid":{"aspectRatio":1.5001155001155002,"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABlMYaKnHYpP8A/8QAGhAAAgMBAQAAAAAAAAAAAAAAAAECESIDEv/aAAgBAQABBQKSPELoitc9I//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABsQAAICAwEAAAAAAAAAAAAAAAERABAhQWFx/9oACAEBAAY/AiV5FWNBx9r/xAAaEAADAAMBAAAAAAAAAAAAAAAAAREhMUGB/9oACAEBAAE/IUPSrkaJYPaM1Sp3ZEo23wM//9oADAMBAAIAAwAAABAz7//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPxCxr//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAgEBPxCVj//EABsQAQADAQADAAAAAAAAAAAAAAEAETEhQWFx/9oACAEBAAE/ECkvP0esLAFAEJLfkUdYY2NE4LT4ZkdC4vk3P//Z","sizes":"(max-width: 3720px) 100vw, 3720px","src":"/static/d1685c5bdd4e5fcff8118cb270a8a607/45a11/muffins.jpg","srcSet":"/static/d1685c5bdd4e5fcff8118cb270a8a607/f8f18/muffins.jpg 930w,\n/static/d1685c5bdd4e5fcff8118cb270a8a607/0e6ff/muffins.jpg 1860w,\n/static/d1685c5bdd4e5fcff8118cb270a8a607/45a11/muffins.jpg 3720w,\n/static/d1685c5bdd4e5fcff8118cb270a8a607/6fb9b/muffins.jpg 5580w,\n/static/d1685c5bdd4e5fcff8118cb270a8a607/a9bec/muffins.jpg 6494w"}}},"author":{"id":"Scott Chaplinski","bio":"Senior Software Engineer","avatar":{"children":[{"fixed":{"src":"/static/9cde8d0f93b2f89dfc7baab094b0567a/c32cc/scott-chaplinski.jpg"}}]}}},"fields":{"layout":"post","slug":"/laravel_factory_migration/"}},"next":{"excerpt":"IntroClueless, but looking to up your game on accessibility. Starting on a new project and wanting to make sure you keep accessibility in…","timeToRead":5,"frontmatter":{"title":"Beginners Tips for Web Accessibility in React","tags":["Engineering"],"date":"2023-02-03T15:24:00.0Z","draft":false,"image":null,"author":{"id":"Keith Potempa","bio":"Software Engineer","avatar":{"children":[{"fixed":{"src":"/static/d2bf68563297faf59bb3d4e587671576/bb49b/keith-potempa.webp"}}]}}},"fields":{"layout":"post","slug":"/a11y/"}},"primaryTag":"Engineering"}}