Talking to the Web

UDP

TCP

HTTP

Rack

Sinatra

Web

Network message

IP packet structure

UDP datagram structure

UDP datagram example

UDP pros/cons

Lightweight

Contains full message

Can broadcast

Pros

Cons

Unreliable

Not ordered

TCP segment structure

TCP handshake

TCP connection lyfecycle

TLS handshake

Server

Ports and Sockets

GET /test HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: theme=light; sessionToken=abc123
Pragma: no-cache
Cache-Control: no-cache

HTTP request format

HTTP verbs

  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • HEAD
  • OPTIONS
  • TRACE
  • CONNECT
HTTP/1.1 200 OK
Content-Encoding: gzip
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Sun, 14 Jul 2019 22:34:41 GMT
Etag: "1541025663+gzip"
Expires: Sun, 21 Jul 2019 22:34:41 GMT
Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
Server: ECS (nyb/1D1E)
Content-Length: 606
set-cookie: theme=light; sessionToken=abc123

<!DOCTYPE html>
<html>
  <head>...</head>
  <body>
    ...
  </body>
</html>

HTTP response format

HTTP status codes

  • 1xx - Informational response

  • 2xx - Success

  • 3xx - Redirection

  • 4xx - Client errors

  • 5xx - Server errors

GET /test HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Firefox/68.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Connection: keep-alive
Cookie: theme=light; sessionToken=abc123
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Date: Sun, 14 Jul 2019 22:34:41 GMT
Content-Length: 606
set-cookie: theme=light; sessionToken=abc123

<!DOCTYPE html>
<html>...</html>

HTTP cookies

Request:

Response:

URI: Uniform Resource Identifier

            userinfo           host      port
        ┌──────┴────────┐ ┌──────┴──────┐ ┌┴┐
https://username:p4ssw0rd@www.example.com:123/articles/?tag=networking&order=newest#top
└─┬─┘   └───────────────┬───────────────────┘└───┬────┘ └───────────┬─────────────┘ └┬┘
scheme              authority                   path              query           fragment

mailto:John.Doe@example.com
└─┬──┘ └────┬─────────────┘
scheme     path

tel:+1-816-555-1212
└┬┘ └──────┬──────┘
scheme    path
URI = scheme:[//authority]path[?query][#fragment]
authority = [userinfo@]host[:port]

Server

Web server

Web server

Web server

HTTP request

HTTP response

Simplest web server in ruby

require 'socket'

server = TCPServer.new(6543)

loop do
  conn = server.accept

  request = []
  while line = conn.gets
    request << line
    break if line == "\n"
  end

  conn.puts <<~HTTP
    HTTP/1.1 200 OK
    Content-type: text/plain; charset=utf-8

    Hello world!
  HTTP

  conn.close
end

Manage connections

Recieve HTTP requests

Provide HTTP responses

HTTP server + web application

require 'socket'

class HTTPServer
  def initialize(app, port: 6543)
    @server = TCPServer.new(port)
    @app = app
  end

  def run
    loop do
      conn = @server.accept

      request = get_request(conn)
      env = parse_request(request)

      status, headers, body = @app.call(env)

      conn.puts build_response(status, headers, body)
      conn.close
    end
  end

  # ...
end

app = proc do |env|
  [200, { 'Content-type' => 'text/plain' }, ['Hello world!']]
end

HTTPServer.new(app).run

HTTP server: lowlevel stuff

web app: response logics

web app interface

rack: basic example

# config.ru

app = proc do |env|
  ['200', { 'Content-Type' => 'text/html' }, ["<h1>Hello rack!</h1>"]]
end

run app
$ rackup config.ru
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:9292

rack: env

rack: middleware

# config.ru
class Cache
  def initialize(app)
    @app = app
    @cache = {}
  end

  def call(env)
    path = env['PATH_INFO']

    if @cache.key?(path)
      puts "Returning cached response for #{path}"
    else
      @cache[path] = @app.call(env)
    end

    @cache[path]
  end
end

inner_app = proc do |env|
  puts "Inner app called with #{env}"
  sleep 2 # simulate long-running process
  [200, { 'Content-type' => 'text/plain' }, ['Hello middleware!']]
end

wrapped_app = Cache.new(inner_app)

run wrapped_app

rack: middleware

rack: complex example

class App
  def call(env)
    case env['PATH_INFO']

    when '/'        then root_action(env)
    when '/posts'   then posts_index_action(env)
    when '/authors' then authors_index_action(env)
    when '/about'   then about_action(env)
    # ...
    else               not_found_action(env)

    end
  end

  private

  def posts_index_action(env)
    posts = get_all_posts
    posts_html = posts_template(posts)

    ['200', { 'Content-Type' => 'text/plain' }, [posts_html]]
  end

  def posts_template
    <<~HTML
      <!DOCTYPE html>
      <html>
        <head></head>
        <body>
          <h1>Posts/h1>
          <ul>
            # ...
          </ul>
        </body>
      </html>
    HTML
  end

  # ...
end

run App.new

Routing

App logics

Templates

Web framework

Sinatra

+

Rails

Hanami

Sinatra

Sinatra

require 'sinatra'
require 'haml'
require 'redis'

set :redis, Redis.new(url: 'redis://127.0.0.1:6379/0')

get '/secret' do
  secret = settings.redis.get('secret') || 'secret is not set yet'
  haml :secret, locals: { secret: secret }
end

post '/secret' do
  new_secret = params.fetch('secret') { halt 422 }
  settings.redis.set('secret', new_secret)

  redirect to('/secret')
end

error(422) { 'Unprocessable Entity' }

__END__

@@ layout
%html
  = yield

@@ secret
Current secret:
%strong= secret

%form{method: 'POST', action: '/secret'}
  %input{name: 'secret', placeholder: 'Enter new secret'}
  %button{type: 'submit'} Save

Sinatra: app structure

Sinatra: links

Intro to sinatra: http://sinatrarb.com/intro.html

Small guide: http://bit.ly/2XKyttg

Modular sinatra: https://oreil.ly/2LUV3Z5

Best practices, part 1: http://bit.ly/2XKsQeF

Best practices, part 2: http://bit.ly/30BQeYO

The end

Go try it!

Talking to the Web

By vizvamitra

Talking to the Web

  • 767