रूबी मैजिक सीरीज़ में हम सॉफ़्टवेयर को अलग रखना पसंद करते हैं ताकि यह सीख सकें कि यह हुड के तहत कैसे काम करता है। यह सब प्रक्रिया के बारे में है; अंतिम परिणाम कुछ ऐसा नहीं है जिसे आप उत्पादन में उपयोग करेंगे, हम रूबी भाषा और इसके लोकप्रिय पुस्तकालयों के आंतरिक कामकाज के बारे में सीखते हैं। हम महीने में लगभग एक बार एक नया लेख प्रकाशित करते हैं, इसलिए यदि आप भी इस तरह के विषय में हैं तो हमारे न्यूज़लेटर की सदस्यता अवश्य लें।
रूबी मैजिक के पुराने संस्करण में हमने रूबी में 30-लाइन HTTP सर्वर लागू किया था। बहुत सारे कोड लिखे बिना, हम HTTP GET अनुरोधों को संभालने और एक साधारण रैक एप्लिकेशन की सेवा करने में सक्षम थे। इस बार, हम अपने होम मेड सर्वर को थोड़ा और आगे ले जाएंगे। जब हम काम पूरा कर लेंगे, तो हमारे पास एक वेब सर्वर होगा जो रेल के प्रसिद्ध पंद्रह मिनट के ब्लॉग की सेवा कर सकता है जो आपको पोस्ट बनाने, अपडेट करने और हटाने की अनुमति देता है।
जहां से हमने छोड़ा था
पिछली बार, हमने एक उदाहरण एप्लिकेशन के रूप में रैक ::लॉबस्टर की सेवा करने के लिए पर्याप्त सर्वर लागू किया था।
- हमारे कार्यान्वयन ने एक टीसीपी सर्वर खोला और एक अनुरोध के आने की प्रतीक्षा की।
- जब ऐसा हुआ, अनुरोध-पंक्ति (
GET /?flip=left HTTP/1.1\r\n
) अनुरोध विधि प्राप्त करने के लिए पार्स किया गया था (GET
), पथ (/
), और क्वेरी पैरामीटर (flip=left
)। - अनुरोध विधि, पथ और क्वेरी स्ट्रिंग को रैक ऐप को पास कर दिया गया, जिसने एक स्थिति, कुछ प्रतिक्रिया शीर्षलेख और प्रतिक्रिया निकाय के साथ एक ट्रिपलेट लौटा दिया।
- उनका उपयोग करके, हम एक नए अनुरोध के आने की प्रतीक्षा करने के लिए कनेक्शन बंद करने से पहले, ब्राउज़र पर वापस भेजने के लिए एक HTTP प्रतिक्रिया बनाने में सक्षम थे।
# http_server.rb
require 'socket'
require 'rack'
require 'rack/lobster'
app = Rack::Lobster.new
server = TCPServer.new 5678
#1
while session = server.accept
request = session.gets
puts request
#2
method, full_path = request.split(' ')
path, query = full_path.split('?')
#3
status, headers, body = app.call({
'REQUEST_METHOD' => method,
'PATH_INFO' => path,
'QUERY_STRING' => query
})
#4
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
हम उस कोड को जारी रखेंगे जो हमने पिछली बार लिखा था। यदि आप साथ चलना चाहते हैं, तो यहां वह कोड है जिसे हमने समाप्त किया है।
रैक और रेल
रेल और सिनात्रा जैसे रूबी ढांचे रैक इंटरफेस के शीर्ष पर बनाए गए हैं। Rack::Lobster
. के उदाहरण की तरह ही हम अभी अपने सर्वर का परीक्षण करने के लिए उपयोग कर रहे हैं, रेल 'Rails.application
एक रैक अनुप्रयोग वस्तु है। सिद्धांत रूप में, इसका मतलब यह होगा कि हमारा सर्वर पहले से ही एक रेल एप्लिकेशन की सेवा करने में सक्षम होना चाहिए।
इसका परीक्षण करने के लिए, मैंने एक साधारण रेल एप्लिकेशन तैयार किया है। आइए इसे हमारे सर्वर के समान निर्देशिका में क्लोन करें।
$ ls
http_server.rb
$ git clone https://github.com/jeffkreeftmeijer/wups.git blog
Cloning into 'blog'...
remote: Counting objects: 162, done.
remote: Compressing objects: 100% (112/112), done.
remote: Total 162 (delta 32), reused 162 (delta 32), pack-reused 0
Receiving objects: 100% (162/162), 29.09 KiB | 0 bytes/s, done.
Resolving deltas: 100% (32/32), done.
Checking connectivity... done.
$ ls
blog http_server.rb
फिर, हमारे सर्वर में, rack
. के बजाय रेल एप्लिकेशन की पर्यावरण फ़ाइल की आवश्यकता होती है और rack/lobster
, और Rails.application
. डालें app
. में Rack::Lobster.new
. के बजाय चर ।
# http_server.rb
require 'socket'
require_relative 'blog/config/environment'
app = Rails.application
server = TCPServer.new 5678
# ...
सर्वर शुरू करना (ruby http_server.rb
) और https://localhost:5678 खोलना हमें दिखाता है कि हम अभी तक वहां नहीं पहुंचे हैं। सर्वर क्रैश नहीं होता है, लेकिन ब्राउज़र में एक आंतरिक सर्वर त्रुटि के साथ हमारा स्वागत है।
अपने सर्वर के लॉग की जाँच करते हुए, हम देख सकते हैं कि हमें rack.input
नाम की कोई चीज़ याद आ रही है . यह पता चला है कि पिछली बार हमारे सर्वर को लागू करते समय हम आलसी हो गए थे, इसलिए इस रेल एप्लिकेशन को काम पर लाने से पहले हमें और काम करना है।
$ ruby http_server.rb
GET / HTTP/1.1
Error during failsafe response: Missing rack.input
...
http_server.rb:15:in `<main>'
रैक वातावरण
वापस जब हमने अपने सर्वर को लागू किया, तो हमने रैक पर्यावरण पर प्रकाश डाला और रैक अनुप्रयोगों को ठीक से सेवा देने के लिए आवश्यक अधिकांश चरों को अनदेखा कर दिया। हमने केवल REQUEST_METHOD
. को ही लागू किया है , PATH_INFO
, और QUERY_STRING
चर, क्योंकि वे हमारे सरल रैक ऐप के लिए पर्याप्त थे।
जैसा कि हमने पहले ही अपवाद से देखा है जब हमने अपना नया एप्लिकेशन शुरू करने का प्रयास किया, रेल को rack.input
की आवश्यकता है , जिसका उपयोग कच्चे HTTP POST डेटा के लिए इनपुट स्ट्रीम के रूप में किया जाता है। इसके अलावा, कुछ और वेरिएबल भी हैं जिन्हें हमें पास करने की आवश्यकता है, जैसे सर्वर का पोर्ट नंबर, और अनुरोध कुकी डेटा।
सौभाग्य से, रैक Rack::Lint
provides प्रदान करता है यह सुनिश्चित करने में मदद करने के लिए कि रैक वातावरण में सभी चर मौजूद हैं और मान्य हैं। हम Rack::Lint.new
पर कॉल करके अपने रेल ऐप को इसमें लपेटकर अपने सर्वर का परीक्षण करने के लिए इसका उपयोग कर सकते हैं। और Rails.application
पास करना ।
# http_server.rb
require 'socket'
require_relative 'blog/config/environment'
app = Rack::Lint.new(Rails.application)
server = TCPServer.new 5678
# ...
Rack::Lint
एक अपवाद फेंक देगा जब पर्यावरण में एक चर गायब या अमान्य है। अभी, हमारे सर्वर को फिर से शुरू करने और https://localhost:5678 खोलने से सर्वर क्रैश हो जाएगा और Rack::Lint
हमें पहली त्रुटि के बारे में सूचित करेगा:SERVER_NAME
चर सेट नहीं किया गया था।
~/Appsignal/http-server (master) $ ruby http_server.rb
GET / HTTP/1.1
/Users/jeff/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/lint.rb:20:in `assert': env missing required key SERVER_NAME (Rack::Lint::LintError)
...
from http_server.rb:15:in `<main>'
हम पर आने वाली प्रत्येक त्रुटि को ठीक करके, हम Rack::Lint
तक चर जोड़ते रह सकते हैं हमारे सर्वर को क्रैश करना बंद कर देता है। आइए प्रत्येक वेरिएबल पर चलते हैं Rack::Lint
आवश्यकता है।
SERVER_NAME
:सर्वर का होस्टनाम। हम अभी इस सर्वर को केवल स्थानीय रूप से चला रहे हैं, इसलिए हम "लोकलहोस्ट" का उपयोग करेंगे।SERVER_PORT
:हमारा सर्वर जिस पोर्ट पर चल रहा है। हमने पोर्ट नंबर (5678) को हार्डकोड किया है, इसलिए हम इसे केवल रैक परिवेश में भेजेंगे।rack.version
:लक्षित रैक प्रोटोकॉल पूर्णांकों की एक सरणी के रूप में संस्करण संख्या।[1,3]
लिखते समय।rack.input
:कच्चे HTTP पोस्ट डेटा युक्त इनपुट स्ट्रीम। हम इस पर बाद में पहुंचेंगे, लेकिन हम एक खालीStringIO
पास करेंगे उदाहरण (एक ASCII-8BIT एन्कोडिंग के साथ) अभी के लिए।rack.errors
:Rack::Logger
. के लिए त्रुटि स्ट्रीम को लिखने के लिए। हम$stderr
. का उपयोग कर रहे हैं ।rack.multithread
:हमारा सर्वर सिंगल-थ्रेडेड है, इसलिए इसेfalse
. पर सेट किया जा सकता है ।rack.multiprocess
:हमारा सर्वर एक ही प्रक्रिया में चल रहा है, इसलिए इसेfalse
. पर सेट किया जा सकता है साथ ही।rack.run_once
:हमारा सर्वर एक प्रक्रिया में कई अनुक्रमिक अनुरोधों को संभाल सकता है, इसलिए यहfalse
है भी।rack.url_scheme
:कोई एसएसएल समर्थन नहीं, इसलिए इसे "https" के बजाय "http" पर सेट किया जा सकता है।
सभी अनुपलब्ध चर जोड़ने के बाद, Rack::Lint
हमारे पर्यावरण में एक और समस्या के बारे में हमें सूचित करेगा।
$ ruby http_server.rb
GET / HTTP/1.1
/Users/jeff/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/lint.rb:20:in `assert': env variable QUERY_STRING has non-string value nil (Rack::Lint::LintError)
...
from http_server.rb:18:in `<main>'
जब अनुरोध में कोई क्वेरी स्ट्रिंग नहीं होगी, तो अब हम nil
पास करेंगे QUERY_STRING
. के रूप में , जिसकी अनुमति नहीं है। उस स्थिति में, रैक इसके बजाय एक खाली स्ट्रिंग की अपेक्षा करता है। लापता वेरिएबल्स को लागू करने और क्वेरी स्ट्रिंग को अपडेट करने के बाद, हमारा परिवेश ऐसा दिखता है:
# http_server.rb
# ...
method, full_path = request.split(' ')
path, query = full_path.split('?')
input = StringIO.new
input.set_encoding 'ASCII-8BIT'
status, headers, body = app.call({
'REQUEST_METHOD' => method,
'PATH_INFO' => path,
'QUERY_STRING' => query || '',
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => '5678',
'rack.version' => [1,3],
'rack.input' => input,
'rack.errors' => $stderr,
'rack.multithread' => false,
'rack.multiprocess' => false,
'rack.run_once' => false,
'rack.url_scheme' => 'http'
})
session.print "HTTP/1.1 #{status}\r\n"
# ...
सर्वर को फिर से शुरू करने और https://localhost:5678 पर फिर से जाने पर, हमें रेल्स के "यू आर ऑन रेल्स!" -पेज के साथ बधाई दी जाएगी, जिसका अर्थ है कि अब हम अपने होम मेड सर्वर पर एक वास्तविक रेल एप्लिकेशन चला रहे हैं!पी>
HTTP POST निकायों को पार्स करना
यह एप्लिकेशन सिर्फ उस इंडेक्स पेज से ज्यादा है। https://localhost:5678/posts पर जाने से पोस्ट की एक खाली सूची प्रदर्शित होगी। यदि हम नया पोस्ट फॉर्म भरकर और "पोस्ट बनाएं" दबाकर एक नया पोस्ट बनाने का प्रयास करते हैं, तो हमें ActionController::InvalidAuthenticityToken
द्वारा बधाई दी जाती है। अपवाद।
प्रपत्र पोस्ट करते समय प्रामाणिकता टोकन साथ भेजा जाता है और यह जांचने के लिए उपयोग किया जाता है कि अनुरोध किसी विश्वसनीय स्रोत से आया है या नहीं। हमारा सर्वर अभी POST डेटा को पूरी तरह से अनदेखा कर रहा है, इसलिए टोकन नहीं भेजा गया है, और अनुरोध को सत्यापित नहीं किया जा सकता है।
जब हमने पहली बार अपना HTTP सर्वर लागू किया था, तब हमने session.gets
. का उपयोग किया था पहली पंक्ति (जिसे अनुरोध-पंक्ति कहा जाता है) प्राप्त करने के लिए, और उस से HTTP विधि और पथ को पार्स किया। अनुरोध-पंक्ति को पार्स करने के अलावा, हमने शेष अनुरोध पर ध्यान नहीं दिया।
POST डेटा निकालने में सक्षम होने के लिए, हमें पहले यह समझना होगा कि HTTP अनुरोध कैसे संरचित किया जाता है। एक उदाहरण को देखते हुए, हम देख सकते हैं कि संरचना एक HTTP प्रतिक्रिया के समान है:
POST /posts HTTP/1.1\r\n
Host: localhost:5678\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: en-us\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Origin: https://localhost:5678\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14\r\n
Cookie: _wups_session=LzE0Z2hSZFNseG5TR3dEVEwzNE52U0lFa0pmVGlQZGtZR3AveWlyMEFvUHRPeXlQUzQ4L0xlKzNLVWtqYld2cjdiWkpmclZIaEhJd1R6eDhaZThFbVBlN2p6QWpJdllHL2F4Z3VseUZ6NU1BRTU5Y1crM2lLRVY0UzdSZkpwYkt2SGFLZUQrYVFvaFE0VjZmZlIrNk5BPT0tLUpLTHQvRHQ0T3FycWV0ZFZhVHZWZkE9PQ%3D%3D--4ef4508c936004db748da10be58731049fa190ee\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
Referer: https://localhost:5678/posts/new\r\n
Content-Length: 369\r\n
\r\n
utf8=%E2%9C%93&authenticity_token=3fu7e8v70K0h9o%2FGNiXxaXSVg3nZ%2FuoL60nlhssUEHpQRz%2BM4ZIHjQduQMexvXrNoC2pjmhNPI4xNNA0Qkh5Lg%3D%3D&post%5Btitle%5D=My+first+post&post%5Bcreated_at%281i%29%5D=2017&post%5Bcreated_at%282i%29%5D=1&post%5Bcreated_at%283i%29%5D=23&post%5Bcreated_at%284i%29%5D=18&post%5Bcreated_at%285i%29%5D=47&post%5Bbody%5D=It+works%21&commit=Create+Post
एक प्रतिक्रिया की तरह, एक HTTP अनुरोध में निम्न शामिल हैं:
- एक अनुरोध-पंक्ति (
POST /posts HTTP/1.1\r\n
), एक विधि टोकन से मिलकर (POST
), एक अनुरोध यूआरआई (/posts/
), और HTTP संस्करण (HTTP/1.1
), उसके बाद एक CRLF (एक कैरिज रिटर्न:\r, उसके बाद लाइन फीड:\n) लाइन के अंत को इंगित करने के लिए - हेडर लाइन्स (
Host: localhost:5678\r\n
) शीर्ष लेख कुंजी, उसके बाद एक कोलन, फिर मान और एक सीआरएलएफ। - अनुरोध लाइन और हेडर को बॉडी से अलग करने के लिए एक नई लाइन (या डबल सीआरएलएफ):(
\r\n\r\n
) - URL ने POST बॉडी को एन्कोड किया
session.gets
. का उपयोग करने के बाद अनुरोध की पहली पंक्ति (अनुरोध-पंक्ति) लेने के लिए, हमारे पास कुछ हेडर लाइन और एक बॉडी बची है। हेडर लाइन प्राप्त करने के लिए, हमें सत्र से लाइनों को पुनः प्राप्त करने की आवश्यकता है जब तक कि हमें एक नई लाइन नहीं मिल जाती (\r\n
)।
प्रत्येक हेडर लाइन के लिए, हम पहले कोलन पर विभाजित होंगे। बृहदान्त्र से पहले सब कुछ कुंजी है, और बाद में सब कुछ मूल्य है। हम #strip
अंत से नई पंक्ति को हटाने का मूल्य।
यह जानने के लिए कि हमें बॉडी प्राप्त करने के अनुरोध से कितने बाइट्स पढ़ने की आवश्यकता है, हम "Content-Length" हेडर का उपयोग करते हैं, जो अनुरोध भेजते समय ब्राउज़र स्वचालित रूप से शामिल हो जाता है।
# http_server.rb
# ...
headers = {}
while (line = session.gets) != "\r\n"
key, value = line.split(':', 2)
headers[key] = value.strip
end
body = session.read(headers["Content-Length"].to_i)
# ...
अब, एक खाली वस्तु भेजने के बजाय, हम एक StringIO
भेजेंगे उदाहरण शरीर के साथ हमें अनुरोध के माध्यम से प्राप्त हुआ। साथ ही, चूंकि अब हम अनुरोध के शीर्षलेख से कुकीज़ को पार्स कर रहे हैं, हम उन्हें HTTP_COOKIE
में रैक वातावरण में जोड़ सकते हैं। अनुरोध प्रामाणिकता जांच पास करने के लिए चर।
# http_server.rb
# ...
status, headers, body = app.call({
# ...
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_COOKIE' => headers['Cookie'],
'rack.version' => [1,3],
'rack.input' => StringIO.new(body),
'rack.errors' => $stderr,
# ...
})
# ...
हम वहाँ चलें। यदि हम सर्वर को पुनरारंभ करते हैं और फ़ॉर्म को फिर से सबमिट करने का प्रयास करते हैं, तो आप देखेंगे कि हमने सफलतापूर्वक अपने ब्लॉग पर पहली पोस्ट बना ली है!
हमने इस बार अपने वेब सर्वर को गंभीरता से अपग्रेड किया है। रैक ऐप से केवल जीईटी अनुरोध स्वीकार करने के बजाय, अब हम एक पूर्ण रेल ऐप की सेवा कर रहे हैं जो POST अनुरोधों को संभालता है। और हमने अभी तक कुल कोड की पचास से अधिक पंक्तियाँ नहीं लिखी हैं!
यदि आप हमारे नए और बेहतर सर्वर के साथ खेलना चाहते हैं, तो यह कोड है। यदि आप अधिक जानना चाहते हैं, या कोई विशिष्ट प्रश्न पूछना चाहते हैं, तो हमें @AppSignal पर बताएं।