Posts

HTTP server in Ruby 3 - Fibers & Ractors

Image
This is part #2. Head over to part #1 to learn about HTTP in Ruby. Motivation Historically Ruby's been lacking in the concurrency department. Ruby has "native" threads (prior to 1.9 there were only "green"), which means there can be multiple threads controlled by an OS, but only 1 thread can be executed at a time, this is managed via Global Interpreter Lock (GIL). However, native calls and I/O calls can be executed in parallel. During an I/O call, a thread gives up control and waits for a signal that the I/O has finished. This means I/O heavy applications can benefit from multiple threads. In this article we are going to explore different concurrency modes for our HTTP server: Single-threaded Multi-threaded Fibers Ractors Single-threaded Let's start simple and see how a single threaded HTTP server could look like ( full code ): def start socket = TCPServer.new(HOST, PORT) sock

Ruby HTTP server from the ground up

Image
Getting something to work quickly is important when you are starting out, but if you want to become better at programming it's important to know a few levels below the abstractions you are used to be working with. When it comes to Web development it's important to know how HTTP works, and what better way to do that than go through baptism by fire and build our own HTTP server. How does HTTP look anyway? HTTP is plaintext protocol implemented over TCP so we can easily inspect what requests look like ( HTTP 2 is actually no longer plaintext, it's binary for efficiency purposes ). One way to look at request structure is to use curl with -v (verbose) flag: curl http://example.com/something -H "x-some-header: value" -v Outputs GET /something HTTP/1.1 Host: example.com User-Agent: curl/7.64.1 Accept: */* x-some-header: value And in response we get HTTP/1.1 404 Not Found Age: 442736 Cache-Control: max-age=604800 Content-Type:

Track Opened Emails In Rails

Image
Tracking opened emails is a technique that, if used responsibly, can provide utility to both you and your users. Here a few use cases where it makes sense to me: Appointment updates. If your appointment in a car dealership was rescheduled an email is sent. If the email is not read in the next couple of hours a repeat email can be sent or a CS agent may follow up with a call. Uber-style fulfillment, where contractors are notified of lucrative jobs available to them. If nobody claims a job, a CS agent can go through users who are known not to have read the email. Providing estimates to event organizers how many people are going to show up based a on number of opened emails. In this article, we are going to implement such a system for transactional emails using Rails and Postmark (a service that sends emails). We are going to use the Postmark gem . Usage First of all, let's see how the code usage of the tool we are building is going to look like

Turbocharge HTTP requests in Ruby

Image
The problem The slowest part in many applications is I/O, especially network I/O. We spend a lot of trying to make sure we reduce the number of calls and cache results of API calls to 3rd party services and resources. Imagine we want to get data from Rick and Morty API . In this article, we are going to speed up subsequent requests to this API by almost 4x times . The solution And yet there's a trick that even very senior developers and popular API library/clients forget about that can shave off precious time of your network calls built right into HTTP. Establishing an HTTP connection is very costly, especially if it uses TLS . This is a fixed price added to your HTTP calls that can be avoided by using keep-alive - a mechanism built into HTTP. According to Wiki : HTTP keep-alive, or HTTP connection reuse, is the idea of using a single TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new connection

Next.js: restrict pages to authenticated users

Image
Many websites have pages that need to be restricted to authenticated users, for example profile pages or dashboards. Next.js web framework comes with a lot of functionality built-in JS compilation, rendering code on server and caching. When it comes to most other aspecs, it's up to a programmer to build, including such needed functionality as restricting access to some pages only to authenticated users. In this article we are going to build just that: a function that augments a page object, that could be used like that: Here's the implementation in TypeScript: In order to make it work on server we are going to need one more utility function fetchWithCookies . When making HTTP request from inside a Web Browser, the browser automatically sends cookies with each requests and then stores new cookies if a response comes in with set-cookie header. On server-side we need to build this feature ourselves, because neither Next.js nor NodeJS make any a

Phone Authentication in Ruby

Image
Using SMS to authenticate a user has the following benefits: Everybody has a phone. Users don't have to remember passwords. Protect against robots & duplicate accounts. Familiarity. You should not use SMS a sole authentication method for systems that require high-security. What I am going to cover in this article is how to make an API that generates "phone tokens" that can be used to sign-in / sign-up. This way you don't need to create a user record before a phone is verified. User stories Below is 2 user stories we are going to cover. As a returning user. I want to sign-in . I go to the login screen and enter my phone . I receive an SMS with the verification code, enter the code . I am logged in. As a new user. I want to sign-up . I go to the login screen and enter my phone . I receive an SMS with the verification code, enter the code . I

Encrypt & Decrypt text in Ruby

If you want to pass some information to a client that you don't want the client to read or temper with, read on! The usage of our TextEncryptor is straightforward: data = { ... } serialized_data = JSON.dump(data) encoded_data = TextEncryptor.encrypt(serialized_data) # ... serialized_data = TextEncryptor.decrypt(encoded_data) data = JSON.parse(serialized_data) Implementation: One of the ways I use this is to store a proof that a phone number is verified on client side and allow exchanging that encoded phone number token to login or signup. You can read more about this in one of the following blog posts. Thank you for reading!