Easy ruby Web server 
Tags: ruby, webserver
Published: 2018-12-01 13:04:21

---
title: Easy ruby Web server 
published: true
description: 
tags: ruby, web server
---

I read `Working With Unix Processes`. It's a very interesting book because I didn't know about Unix Processes. I've used Web servers in my work with not knowing how it does. So I wanted to write this article to remember what I learn.
In this article, I tried to creat easy web server using ruby.

# What is a Web server

It accepts requests from clients(users), then it returns a response. Web server I'm creating now is very simple, it handles the HTTP GET requests correctly. 

# Launch TCP server

Ruby provides `TCPServer` class, it's easy to create TCP connection for me.
server.rb
```rb
require 'socket'

server = TCPServer.open('0.0.0.0', 5678)

while connection = server.accept
  connection.write "Hello world!!"
  connection.close 
end
```

Since keepalive is not supported we can close the client connection, immediately after writing the body.

I can launch a server to do `ruby server.rb`, you can access to `curl http://localhost:5678`, then it shows `Hello world!!`
`server.rb` handle and shuts down your request. I'm ready to make HTTP server because HTTP is on TCP, it's just added some header information.

# Getting started on HTTP Server

Although now this communications on TCP layer, I have to create HTTP protocol to archive my goal.
As I said, HTTP needs some header information. So I add some HTTP headers

```rb
  head = "HTTP/1.1 200\r\n" \
  "Date: #{Time.now.httpdate}\r\n" \
  "Content-Length: #{body.length.to_s}\r\n" 
```

Whole of code 

server.rb

```rb
require 'socket'
require 'time'

server = TCPServer.open('0.0.0.0', 5678)

while connection = server.accept

  body = "Hello world!"

  head = "HTTP/1.1 200\r\n" \
  "Date: #{Time.now.httpdate}\r\n" \
  "Content-Length: #{body.length.to_s}\r\n" 

  # 1
  connection.write head

  # 2
  connection.write "\r\n"

  # 3
  connection.write body

  session.close
  connection.close 
end
```

1. It adds http header's info 
2. It represents characters(a CRLF) between header and body
3. It add body(It'll be shown in browser. normally it's html, json and xml etc)

Then, finally we can see the response in your browser. It returns 200 with "Hello world" response if you access `localhost:5678`
And I could understand that HTTP is just added Header information on the TCP server.

## Real world

This can catch only GET and response 200 always. It doesn't look like be a real Web server.
Then the next step I want to control status, header, and body as I want. At first, I install `Rack` it is a famous middleware to connect Web server and application(Like Rails).
[Rack](http://rack.github.io/) provides the just interface 

The code will be like this
```rb
require 'socket'
require 'time'
require 'rack/utils'

server = TCPServer.open('0.0.0.0', 5678)

# 1
app = Proc.new do |env|
  body = "Hello world!"
  ['200', {'Content-Type' => 'text/html', "Content-Length" => body.length.to_s}, ["Hello world!"]]
end

while connection = server.accept
  # 2
  status, headers, body = app.call({})

  head = "HTTP/1.1 200\r\n" \
  "Date: #{Time.now.httpdate}\r\n" \
  "Status: #{Rack::Utils::HTTP_STATUS_CODES[status]}\r\n" 

  # 3
  headers.each do |k,v|
    head << "#{k}: #{v}\r\n"
  end

  connection.write "#{head}\r\n"

  # 3
  body.each do |part| 
    connection.write part
  end

  body.close if body.respond_to?(:close)

  connection.close 
end
```

1. It's a setting for the interface which Rack provides
2. It retuns three values, status, header, body
3. It writes body and headeers. Since body and header will be array, each is used in this code

## Reading request

You can get request data using `connection.get`.
`connection.get` is like this `GET / HTTP/1.1`. Then I can extract request path and method etc from client request.
```rb
# 1
method, full_path = request.split(' ')
# 2
path = full_path.split('?')
```

And in `Proc.new` I can route the process by each path.

```
app = Proc.new do |env|
  req = Rack::Request.new(env)
  case req.path
  when "/"
    body = "Hello world!"
    [200, {'Content-Type' => 'text/html', "Content-Length" => body.length.to_s}, [body]]
  when /^\/name\/(.*)/
    body = "Hello, #{$1}!"
    [200, {'Content-Type' => 'text/html', "Content-Length" => body.length.to_s}, [body]]
  else 
    [404, {"Content-Type" => "text/html"}, ["Ah!!!"]]
  end
end
```

Finally, it can do routing, Method and query parameters are easy to control following like this codes

The whole of code
```rb
require 'socket'
require 'time'
require 'rack'
require 'rack/utils'

# app = Rack::Lobster.new
server = TCPServer.open('0.0.0.0', 5678)

app = Proc.new do |env|
  req = Rack::Request.new(env)
  case req.path
  when "/"
    body = "Hello world!"
    [200, {'Content-Type' => 'text/html', "Content-Length" => body.length.to_s}, [body]]
  when /^\/name\/(.*)/
    body = "Hello, #{$1}!"
    [200, {'Content-Type' => 'text/html', "Content-Length" => body.length.to_s}, [body]]
  else 
    [404, {"Content-Type" => "text/html"}, ["Ah!!!"]]
  end
end

while connection = server.accept
  request = connection.gets
  # 1
  method, full_path = request.split(' ')
  # 2
  path = full_path.split('?')

  # 1
  status, headers, body = app.call({
    'REQUEST_METHOD' => method,
    'PATH_INFO' => path
  })

  head = "HTTP/1.1 200\r\n" \
  "Date: #{Time.now.httpdate}\r\n" \
  "Status: #{Rack::Utils::HTTP_STATUS_CODES[status]}\r\n" 

  # 1
  headers.each do |k,v|
    head << "#{k}: #{v}\r\n"
  end

  connection.write "#{head}\r\n"

  body.each do |part| 
    connection.write part
  end

  body.close if body.respond_to?(:close)

  connection.close 
end
```



Next => Easy prefork webserver(if I have a freeeeeee time)