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
- 913