Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Tạo Ruby HTTP Server Chỉ 30 Dòng Code

Web servers, và HTTP nói chung, có vẻ difficult understand. Browser format request như thế nào, response gửi user như thế nào? Episode Ruby Magic này học build Ruby HTTP server 30 lines code. Khi xong, server handle HTTP GET requests và serve Rack app.

HTTP Và TCP Làm Việc Cùng Nhau Như Thế Nào

TCP là transport protocol mô tả server và client exchange data như thế nào.

HTTP là request-response protocol mô tả cụ thể web servers exchange data với HTTP clients/web browsers. HTTP thường dùng TCP as transport protocol. Bản chất, HTTP server là TCP server "speaks" HTTP.

# tcp_server.rb require 'socket' server = TCPServer.new 5678 while session = server.accept session.puts "Hello world! The time is #{Time.now}" session.close end

TCP server example, server bind port 5678 wait client connect. Khi xảy ra, gửi message client, close connection. Xong client đầu, server wait another client connect gửi message lại.

# tcp_client.rb require 'socket' server = TCPSocket.new 'localhost', 5678 while line = server.gets puts line end server.close

Connect server, cần TCP client. Example client connect same port (5678) dùng server.gets nhận data từ server, print. Khi stop nhận data, close connection server, program exit.

Start server ($ ruby tcp_server.rb), start client separate tab nhận server message.

$ ruby tcp_client.rb Hello world! The time is 2016-11-23 15:17:11 +0100 $ 

Với chút imagination, TCP server/client work somewhat như web server/browser. Client gửi request, server respond, connection closed. Đó là request-response pattern, chính cái cần build HTTP server.

Trước good part, xem HTTP requests/responses trông như nào.

Basic HTTP GET Request

Basic HTTP GET request là request-line không additional headers/request body.

GET / HTTP/1.1\r\n 

Request-Line gồm 4 phần:

  • Method token (GET)
  • Request-URI (/)
  • Protocol version (HTTP/1.1)
  • CRLF (carriage return: \r, line feed: \n) end line

Server respond HTTP response, trông như:

HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n\Hello world! 

Response gồm:

  • Status line: protocol version ("HTTP/1.1"), space, response status code ("200"), terminated CRLF (\r\n)
  • Optional header lines. Case này 1 header line ("Content-Type: text/html"), nhưng có thể multiple (separated CRLF: \r\n)
  • Newline (double CRLF) separate status line/headers từ body: (\r\n\r\n)
  • Body: "Hello world!"

Minimal Ruby HTTP Server

Đủ talk. Biết tạo TCP server Ruby và HTTP requests/responses, build minimal HTTP server. Web server trông mostly same TCP server trước. General idea same, chỉ dùng HTTP protocol format message. Vì dùng browser send requests/parse responses, không implement client lần này.

# http_server.rb require 'socket' server = TCPServer.new 5678 while session = server.accept request = session.gets puts request session.print "HTTP/1.1 200\r\n" # 1 session.print "Content-Type: text/html\r\n" # 2 session.print "\r\n" # 3 session.print "Hello world! The time is #{Time.now}" #4 session.close end

Sau server nhận request, như trước, dùng session.print gửi message back client: Thay vì just message, prefix response với status line, header, newline:

  1. Status line (HTTP 1.1 200\r\n) tell browser HTTP version 1.1, response code "200"
  2. Header indicate response text/html content type (Content-Type: text/html\r\n)
  3. Newline (\r\n)
  4. Body: "Hello world! …"

Giống trước, close connection sau send message. Không read request, chỉ print console.

Start server mở https://localhost:5678 browser, thấy "Hello world! …" với current time, như TCP client trước. 🎉

Serve Rack App

Đến nay, server return single response mỗi request. Làm useful hơn, add more responses. Thay vì add vào server directly, dùng Rack app. Server parse HTTP requests pass cho Rack app, return response server gửi back client.

Rack là interface giữa web servers support Ruby và most Ruby web frameworks như Rails, Sinatra. Simplest form, Rack app là object respond_to call return "tiplet", array 3 items: HTTP response code, hash HTTP headers, body.

app = Proc.new do |env| ['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]] end

Example: response code "200", pass "text/html" content type qua headers, body array với string.

Allow server serve responses từ app, turn returned triplet thành HTTP response string. Thay vì always return static response, build response từ triplet returned by Rack app.

# http_server.rb require 'socket' app = Proc.new do ['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]] end server = TCPServer.new 5678 while session = server.accept request = session.gets puts request # 1 status, headers, body = app.call({}) # 2 session.print "HTTP/1.1 #{status}\r\n" # 3 headers.each do |key, value| session.print "#{key}: #{value}\r\n" end # 4 session.print "\r\n" # 5 body.each do |part| session.print part end session.close end

Serve response từ Rack app, changes server:

  1. Get status code, headers, body từ triplet returned by app.call.
  2. Dùng status code build status line
  3. Loop headers add header line mỗi key-value pair hash
  4. Print newline separate status line/headers từ body
  5. Loop body print each part. Vì 1 part body array, print "Hello world" message session trước close.

Reading Requests

Đến nay, server ignore request variable. Không cần vì Rack app always return same response.

Rack::Lobster example app ships với Rack dùng request URL parameters function. Thay vì Proc dùng as app trước, dùng đó testing app từ nay.

# http_server.rb require 'socket' require 'rack' require 'rack/lobster' app = Rack::Lobster.new server = TCPServer.new 5678 while session = server.accept # ...

Mở browser show lobster thay boring string trước. Lobstericious!

"flip!" và "crash!" links link đến /?flip=left và /?flip=crash. Tuy nhiên, follow links, lobster không flip, nothing crash. Server không handle query strings. Nhớ request variable ignored trước? Xem server logs, thấy request strings cho mỗi page.

GET / HTTP/1.1 GET /?flip=left HTTP/1.1 GET /?flip=crash HTTP/1.1 

HTTP request strings include request method ("GET"), request path (/, /?flip=left, /?flip=crash), HTTP version. Dùng info xác định serve gì.

# http_server.rb require 'socket' require 'rack' require 'rack/lobster' app = Rack::Lobster.new server = TCPServer.new 5678 while session = server.accept request = session.gets puts request # 1 method, full_path = request.split(' ') # 2 path, query = full_path.split('?') # 3 status, headers, body = app.call({ 'REQUEST_METHOD' => method, 'PATH_INFO' => path, 'QUERY_STRING' => query }) session.print "HTTP/1.1 #{status}\r\n" headers.each do |key, value| session.print "#{key}: #{value}\r\n" end session.print "\r\n" body.each do |part| session.print part end session.close end

Parse request send request parameters Rack app, split request string up send to Rack app:

  1. Split request string method và full path
  2. Split full path path và query
  3. Pass those to app in Rack environment hash.

Ví dụ, request GET /?flip=left HTTP/1.1\r\n pass to app như:

{ 'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/', 'QUERY_STRING' => '?flip=left' }

Restart server, visit https://localhost:5678, click "flip!"-link flip lobster, click "crash!" link crash web server.

Vừa scratch surface implement HTTP server, server chỉ 30 lines code, nhưng explain basic idea. Accept GET requests, pass request attributes Rack app, send responses browser. Mặc dù không handle request streaming và POST requests, server theoretically serve other Rack apps.

Kết thúc quick look build HTTP server Ruby. Muốn play around server, đây code. Liên hệ @AppSignal nếu muốn biết thêm, hoặc specific question.

Nếu thích bài này, subscribe Ruby Magic newsletter: (roughly) monthly po(r)tion of Ruby.