वेब सर्वर और सामान्य रूप से HTTP को समझना मुश्किल हो सकता है। ब्राउज़र अनुरोध को कैसे प्रारूपित करता है, और उपयोगकर्ता को प्रतिक्रिया कैसे भेजी जाती है? इस रूबी मैजिक एपिसोड में हम सीखेंगे कि कोड की 30 पंक्तियों में रूबी HTTP सर्वर कैसे बनाया जाता है। जब हम काम पूरा कर लेंगे, तो हमारा सर्वर HTTP GET अनुरोधों को संभाल लेगा और हम इसका उपयोग रैक ऐप की सेवा के लिए करेंगे।
HTTP और TCP एक साथ कैसे काम करते हैं
टीसीपी एक परिवहन प्रोटोकॉल है जो बताता है कि कैसे एक सर्वर और क्लाइंट डेटा का आदान-प्रदान करते हैं।
HTTP एक अनुरोध-प्रतिक्रिया प्रोटोकॉल है जो विशेष रूप से वर्णन करता है कि वेब सर्वर HTTP क्लाइंट या वेब ब्राउज़र के साथ डेटा का आदान-प्रदान कैसे करते हैं। HTTP आमतौर पर TCP को अपने ट्रांसपोर्ट प्रोटोकॉल के रूप में उपयोग करता है। संक्षेप में, HTTP सर्वर एक TCP सर्वर है जो 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 सर्वर के इस उदाहरण में, सर्वर 5678
port पोर्ट से जुड़ता है और क्लाइंट के कनेक्ट होने की प्रतीक्षा करता है। जब ऐसा होता है, तो यह क्लाइंट को एक संदेश भेजता है, और फिर कनेक्शन बंद कर देता है। पहले क्लाइंट से बात करने के बाद, सर्वर फिर से अपना संदेश भेजने के लिए दूसरे क्लाइंट के कनेक्ट होने की प्रतीक्षा करता है।
# tcp_client.rb
require 'socket'
server = TCPSocket.new 'localhost', 5678
while line = server.gets
puts line
end
server.close
हमारे सर्वर से जुड़ने के लिए, हमें एक TCP क्लाइंट की आवश्यकता होगी। यह उदाहरण क्लाइंट उसी पोर्ट से कनेक्ट होता है (5678
) और server.gets
. का उपयोग करता है सर्वर से डेटा प्राप्त करने के लिए, जिसे तब प्रिंट किया जाता है। जब यह डेटा प्राप्त करना बंद कर देता है, तो यह सर्वर से कनेक्शन बंद कर देता है और प्रोग्राम बाहर निकल जाएगा।
जब आप प्रारंभ करते हैं तो सर्वर सर्वर चल रहा होता है ($ ruby tcp_server.rb
), आप सर्वर का संदेश प्राप्त करने के लिए क्लाइंट को एक अलग टैब में प्रारंभ कर सकते हैं।
$ ruby tcp_client.rb
Hello world! The time is 2016-11-23 15:17:11 +0100
$
थोड़ी कल्पना के साथ, हमारा टीसीपी सर्वर और क्लाइंट कुछ हद तक एक वेब सर्वर और एक ब्राउज़र की तरह काम करते हैं। क्लाइंट एक अनुरोध भेजता है, सर्वर प्रतिक्रिया करता है, और कनेक्शन बंद हो जाता है। इस प्रकार अनुरोध-प्रतिक्रिया पैटर्न काम करता है, ठीक यही हमें HTTP सर्वर बनाने के लिए आवश्यक है।
इससे पहले कि हम अच्छी बात करें, आइए देखें कि HTTP अनुरोध और प्रतिक्रियाएँ कैसी दिखती हैं।
एक बुनियादी HTTP GET अनुरोध
सबसे बुनियादी HTTP GET अनुरोध बिना किसी अतिरिक्त हेडर या अनुरोध निकाय के एक अनुरोध-पंक्ति है।
GET / HTTP/1.1\r\n
अनुरोध-पंक्ति में चार भाग होते हैं:
- एक विधि टोकन (
GET
, इस उदाहरण में) - अनुरोध-यूआरआई (
/
) - प्रोटोकॉल संस्करण (
HTTP/1.1
) - एक CRLF (एक कैरिज रिटर्न:
\r
, उसके बाद लाइन फ़ीड:\n
) पंक्ति के अंत को इंगित करने के लिए
सर्वर एक HTTP प्रतिक्रिया के साथ प्रतिक्रिया देगा, जो इस तरह दिख सकता है:
HTTP/1.1 200\r\nContent-Type: text/html\r\n\r\n\Hello world!
इस प्रतिक्रिया में निम्न शामिल हैं:
- एक स्थिति रेखा:प्रोटोकॉल संस्करण ("HTTP/1.1"), उसके बाद एक स्थान, प्रतिक्रिया का स्थिति कोड ("200"), और एक CRLF (
\r\n
के साथ समाप्त होता है) ) - वैकल्पिक शीर्षलेख पंक्तियाँ। इस मामले में, केवल एक हेडर लाइन ("सामग्री-प्रकार:टेक्स्ट/एचटीएमएल") है, लेकिन कई हो सकते हैं (सीआरएलएफ से अलग:
\r\n
) - स्थिति रेखा और शीर्षलेख को मुख्य भाग से अलग करने के लिए एक नई पंक्ति (या एक डबल CRLF):(
\r\n\r\n
) - शरीर:"नमस्ते दुनिया!"
एक न्यूनतम रूबी HTTP सर्वर
काफी बात। अब जब हम जानते हैं कि रूबी में एक टीसीपी सर्वर कैसे बनाया जाता है और कुछ HTTP अनुरोध और प्रतिक्रियाएं कैसी दिखती हैं, तो हम एक न्यूनतम HTTP सर्वर बना सकते हैं। आप देखेंगे कि वेब सर्वर ज्यादातर टीसीपी सर्वर जैसा ही दिखता है जिसकी हमने पहले चर्चा की थी। सामान्य विचार वही है, हम अपने संदेश को प्रारूपित करने के लिए केवल HTTP प्रोटोकॉल का उपयोग कर रहे हैं। साथ ही, चूंकि हम अनुरोध भेजने और प्रतिक्रियाओं को पार्स करने के लिए ब्राउज़र का उपयोग करेंगे, इसलिए हमें इस बार क्लाइंट को लागू करने की आवश्यकता नहीं होगी।
# 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
सर्वर को पहले की तरह अनुरोध प्राप्त होने के बाद, यह session.print
. का उपयोग करता है क्लाइंट को एक संदेश वापस भेजने के लिए:केवल हमारे संदेश के बजाय, यह एक स्टेटस लाइन, एक हेडर और एक नई लाइन के साथ प्रतिक्रिया को प्रीफ़िक्स करता है:
- स्थिति रेखा (
HTTP 1.1 200\r\n
) ब्राउज़र को यह बताने के लिए कि HTTP संस्करण 1.1 है और प्रतिक्रिया कोड "200" है - एक शीर्षलेख यह इंगित करने के लिए कि प्रतिक्रिया में टेक्स्ट/एचटीएमएल सामग्री प्रकार है (
सामग्री-प्रकार:टेक्स्ट/एचटीएमएल\r\n
) - नई पंक्ति (
\r\n
) - शरीर:"नमस्ते दुनिया! ..."
पहले की तरह यह मैसेज भेजने के बाद कनेक्शन बंद कर देता है। हम अभी तक अनुरोध नहीं पढ़ रहे हैं, इसलिए यह अभी के लिए इसे कंसोल पर प्रिंट करता है।
यदि आप सर्वर शुरू करते हैं और अपने ब्राउज़र में https://localhost:5678 खोलते हैं, तो आपको "हैलो वर्ल्ड!
रैक ऐप पेश करना
अब तक, हमारा सर्वर प्रत्येक अनुरोध के लिए एक ही प्रतिक्रिया देता रहा है। इसे थोड़ा और उपयोगी बनाने के लिए, हम अपने सर्वर में और प्रतिक्रियाएँ जोड़ सकते हैं। इन्हें सीधे सर्वर से जोड़ने के बजाय, हम एक रैक ऐप का उपयोग करेंगे। हमारा सर्वर HTTP अनुरोधों को पार्स करेगा और उन्हें रैक ऐप में भेज देगा, जो तब सर्वर को क्लाइंट को वापस भेजने के लिए एक प्रतिक्रिया देगा।
रैक वेब सर्वरों के बीच एक इंटरफ़ेस है जो रूबी और अधिकांश रूबी वेब फ्रेमवर्क जैसे रेल और सिनात्रा का समर्थन करता है। अपने सरलतम रूप में, रैक ऐप एक ऐसी वस्तु है जो कॉल
. का जवाब देती है और एक "टिपलेट" लौटाता है, एक सरणी जिसमें तीन आइटम होते हैं:एक HTTP प्रतिक्रिया कोड, HTTP हेडर का एक हैश और एक बॉडी।
app = Proc.new do |env|
['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]]
end
इस उदाहरण में, प्रतिक्रिया कोड "200" है, हम हेडर के माध्यम से सामग्री प्रकार के रूप में "टेक्स्ट/एचटीएमएल" पास कर रहे हैं, और शरीर एक स्ट्रिंग के साथ एक सरणी है।
हमारे सर्वर को इस ऐप से प्रतिक्रिया देने की अनुमति देने के लिए, हमें लौटाए गए ट्रिपलेट को HTTP प्रतिक्रिया स्ट्रिंग में बदलना होगा। हमेशा एक स्थिर प्रतिक्रिया देने के बजाय, जैसा कि हमने पहले किया था, अब हमें रैक ऐप द्वारा लौटाए गए ट्रिपलेट से प्रतिक्रिया बनानी होगी।
# 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
रैक ऐप से हमें जो प्रतिक्रिया मिली है, उसे पूरा करने के लिए, हम अपने सर्वर में कुछ बदलाव करेंगे:
- एप्लिकेशन.कॉल द्वारा लौटाए गए ट्रिपलेट से स्थिति कोड, हेडर और बॉडी प्राप्त करें ।
- स्थिति कोड बनाने के लिए स्थिति कोड का उपयोग करें
- हेडर पर लूप करें और हैश में प्रत्येक की-वैल्यू पेयर के लिए हेडर लाइन जोड़ें
- स्थिति रेखा और शीर्षलेखों को मुख्य भाग से अलग करने के लिए एक नई पंक्ति प्रिंट करें
- शरीर पर लूप करें और प्रत्येक भाग को प्रिंट करें। चूंकि हमारे बॉडी ऐरे में केवल एक हिस्सा है, यह हमारे "हैलो वर्ल्ड"-संदेश को बंद करने से पहले बस प्रिंट कर देगा।
पढ़ने के अनुरोध
अब तक, हमारा सर्वर अनुरोध
. को अनदेखा कर रहा है चर। हमें इसकी आवश्यकता नहीं थी क्योंकि हमारे रैक ऐप ने हमेशा एक ही प्रतिक्रिया दी थी।
रैक::लॉबस्टर
एक उदाहरण ऐप है जो रैक के साथ शिप करता है और कार्य करने के लिए अनुरोध URL पैरामीटर का उपयोग करता है। प्रोक के बजाय हमने पहले एक ऐप के रूप में उपयोग किया था, हम अब से इसे अपने परीक्षण ऐप के रूप में उपयोग करेंगे।
# http_server.rb
require 'socket'
require 'rack'
require 'rack/lobster'
app = Rack::Lobster.new
server = TCPServer.new 5678
while session = server.accept
# ...
ब्राउजर खोलने पर अब पहले छपी बोरिंग स्ट्रिंग के बजाय लॉबस्टर दिखाई देगा। लोबस्टरियस!
"फ्लिप!" और "दुर्घटना!" लिंक /?flip=left
. से लिंक करता है और /?flip=crash
क्रमश। हालांकि, लिंक का अनुसरण करते समय, लॉबस्टर फ्लिप नहीं करता है और अभी तक कुछ भी क्रैश नहीं होता है। ऐसा इसलिए है क्योंकि हमारा सर्वर अभी क्वेरी स्ट्रिंग को हैंडल नहीं करता है। अनुरोध
याद रखें चर जिसे हमने पहले अनदेखा किया था? यदि हम अपने सर्वर के लॉग को देखें, तो हम प्रत्येक पृष्ठ के लिए अनुरोध स्ट्रिंग देखेंगे।
GET / HTTP/1.1
GET /?flip=left HTTP/1.1
GET /?flip=crash HTTP/1.1
HTTP अनुरोध स्ट्रिंग में अनुरोध विधि ("GET"), अनुरोध पथ (/
. शामिल हैं , /?flip=left
और /?flip=crash
), और HTTP संस्करण। हम इस जानकारी का उपयोग यह निर्धारित करने के लिए कर सकते हैं कि हमें क्या परोसना है।
# 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
अनुरोध को पार्स करने और रैक ऐप को अनुरोध पैरामीटर भेजने के लिए, हम अनुरोध स्ट्रिंग को विभाजित करेंगे और इसे रैक ऐप पर भेज देंगे:
- अनुरोध स्ट्रिंग को एक विधि और एक पूर्ण पथ में विभाजित करें
- पूरे पथ को पथ और क्वेरी में विभाजित करें
- रैक वातावरण हैश में उन्हें हमारे ऐप में पास करें।
उदाहरण के लिए, GET /?flip=left HTTP/1.1\r\n
. जैसा अनुरोध इस तरह ऐप को पास कर दिया जाएगा:
{
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/',
'QUERY_STRING' => '?flip=left'
}
हमारे सर्वर को पुनरारंभ करना, https://localhost:5678 पर जाकर, और "फ्लिप!" पर क्लिक करना - लिंक अब लॉबस्टर को फ्लिप करेगा, और "क्रैश!" पर क्लिक करेगा। लिंक हमारे वेब सर्वर को क्रैश कर देगा।
हमने अभी एक HTTP सर्वर को लागू करने की सतह को खरोंच दिया है, और हमारा कोड केवल 30 पंक्तियों का है, लेकिन यह मूल विचार की व्याख्या करता है। यह GET अनुरोधों को स्वीकार करता है, अनुरोध की विशेषताओं को एक रैक ऐप में भेजता है, और ब्राउज़र को प्रतिक्रियाएँ वापस भेजता है। हालांकि यह रिक्वेस्ट स्ट्रीमिंग और POST रिक्वेस्ट जैसी चीजों को हैंडल नहीं करता है, लेकिन सैद्धांतिक रूप से हमारे सर्वर का इस्तेमाल अन्य रैक ऐप्स को भी सर्व करने के लिए किया जा सकता है।
यह रूबी में एक HTTP सर्वर बनाने में हमारे त्वरित रूप को समाप्त करता है। यदि आप हमारे सर्वर के साथ खेलना चाहते हैं, तो यहां कोड है। यदि आप अधिक जानना चाहते हैं, या कोई विशिष्ट प्रश्न पूछना चाहते हैं, तो हमें @AppSignal पर बताएं।
अगर आपको यह लेख अच्छा लगा हो, तो रूबी मैजिक न्यूज़लेटर की सदस्यता लें:रूबी का एक (मोटे तौर पर) मासिक पो (आर) टीयन।