क्या आपने कभी रूबी के साथ अपना स्वयं का वेब सर्वर बनाया है?
हमारे पास पहले से ही कई सर्वर हैं, जैसे:
- प्यूमा
- पतला
- गेंडा
लेकिन मुझे लगता है कि यह सीखने का एक अच्छा अभ्यास . है यदि आप जानना चाहते हैं कि एक साधारण वेब सर्वर कैसे काम करता है।
इस लेख में, आप सीखेंगे कि यह कैसे करना है।
चरण-दर-चरण!
चरण 1:कनेक्शन के लिए सुनना
हम कहाँ से शुरू करें?
सबसे पहले हमें टीसीपी पोर्ट 80 पर नए कनेक्शन सुनने की जरूरत है।
मैंने पहले ही रूबी में नेटवर्क प्रोग्रामिंग के बारे में एक पोस्ट लिखी है, इसलिए मैं यह नहीं बताऊंगा कि यह कैसे काम करता है।
मैं आपको केवल कोड देने जा रहा हूं :
require 'socket'
server = TCPServer.new('localhost', 80)
loop {
client = server.accept
request = client.readpartial(2048)
puts request
}
जब आप इस कोड को चलाते हैं तो आपके पास एक सर्वर होगा जो पोर्ट 80 पर कनेक्शन स्वीकार करता है। यह अभी तक बहुत कुछ नहीं करता है, लेकिन यह आपको यह देखने की अनुमति देगा कि आने वाला अनुरोध कैसा दिखता है।
<ब्लॉकक्वॉट>नोट :Linux/Mac सिस्टम में पोर्ट 80 का उपयोग करने के लिए आपको रूट विशेषाधिकारों की आवश्यकता होगी। एक विकल्प के रूप में, आप 1024 से ऊपर के किसी अन्य पोर्ट का उपयोग कर सकते हैं। मुझे 8080
. पसंद है
अनुरोध उत्पन्न करने का एक आसान तरीका है कि आप अपने ब्राउज़र या curl . जैसी किसी चीज़ का उपयोग करें ।
जब आप ऐसा करेंगे तो आप देखेंगे कि यह आपके सर्वर में छपा हुआ है:
GET / HTTP/1.1 Host: localhost User-Agent: curl/7.49.1 Accept: */*
यह एक HTTP अनुरोध है। HTTP एक सादा पाठ प्रोटोकॉल है जिसका उपयोग वेब ब्राउज़र और वेब सर्वर के बीच संचार के लिए किया जाता है।
आधिकारिक प्रोटोकॉल विनिर्देश यहां पाया जा सकता है:https://tools.ietf.org/html/rfc7230।
चरण 2:अनुरोध को पार्स करना
अब हमें अनुरोध को छोटे घटकों में विभाजित करने की आवश्यकता है जिसे हमारा सर्वर समझ सकता है।
ऐसा करने के लिए हम अपना खुद का पार्सर बना सकते हैं या पहले से मौजूद एक का उपयोग कर सकते हैं। हम अपना स्वयं का निर्माण करने जा रहे हैं, इसलिए हमें यह समझने की आवश्यकता है कि अनुरोध के विभिन्न भागों का क्या अर्थ है।
इस छवि को मदद करनी चाहिए :
अनुरोध प्राप्त करें
हेडर का उपयोग ब्राउज़र कैशिंग, वर्चुअल होस्टिंग और डेटा कम्प्रेशन जैसी चीज़ों के लिए किया जाता है, लेकिन एक बुनियादी कार्यान्वयन के लिए हम उन्हें अनदेखा कर सकते हैं और अभी भी एक कार्यात्मक सर्वर है।
एक साधारण HTTP पार्सर बनाने के लिए हम इस तथ्य का लाभ उठा सकते हैं कि अनुरोध डेटा को नई लाइनों (\r\n के माध्यम से अलग किया जाता है। ) हम चीजों को सरल रखने के लिए कोई त्रुटि या वैधता जाँच नहीं करने जा रहे हैं।
यहाँ वह कोड है जिसके साथ मैं आया था:
def parse(request)
method, path, version = request.lines[0].split
{
path: path,
method: method,
headers: parse_headers(request)
}
end
def parse_headers(request)
headers = {}
request.lines[1..-1].each do |line|
return headers if line == "\r\n"
header, value = line.split
header = normalize(header)
headers[header] = value
end
def normalize(header)
header.gsub(":", "").downcase.to_sym
end
end
यह पार्स किए गए अनुरोध डेटा के साथ एक हैश लौटाएगा। अब जब हमारे पास एक प्रयोग करने योग्य प्रारूप में हमारा अनुरोध है तो हम ग्राहक के लिए अपनी प्रतिक्रिया तैयार कर सकते हैं।
चरण 3:प्रतिक्रिया तैयार करना और भेजना
प्रतिक्रिया बनाने के लिए हमें यह देखना होगा कि अनुरोधित संसाधन उपलब्ध है या नहीं। दूसरे शब्दों में, हमें यह जांचना होगा कि फ़ाइल मौजूद है या नहीं।
ऐसा करने के लिए मैंने जो कोड लिखा है वह यहां दिया गया है:
SERVER_ROOT = "/tmp/web-server/"
def prepare_response(request)
if request.fetch(:path) == "/"
respond_with(SERVER_ROOT + "index.html")
else
respond_with(SERVER_ROOT + request.fetch(:path))
end
end
def respond_with(path)
if File.exists?(path)
send_ok_response(File.binread(path))
else
send_file_not_found
end
end
यहां दो चीजें हो रही हैं :
- सबसे पहले, यदि पथ
/पर सेट है हम मानते हैं कि हमें जो फ़ाइल चाहिए वह हैindex.html। - दूसरा, यदि अनुरोधित फ़ाइल मिलती है, तो हम फ़ाइल सामग्री को ठीक प्रतिक्रिया के साथ भेजने जा रहे हैं।
लेकिन अगर फाइल नहीं मिलती है तो हम ठेठ 404 Not Found . भेजने जा रहे हैं प्रतिक्रिया।
सबसे सामान्य HTTP प्रतिक्रिया कोड की तालिका
संदर्भ के लिए।
| Code | विवरण |
|---|---|
| 200 | ठीक |
| 301 | स्थायी रूप से स्थानांतरित हो गया |
| 302 | मिला |
| 304 | संशोधित नहीं |
| 400 | खराब अनुरोध |
| 401 | अनधिकृत |
| 403 | वर्जित |
| 404 | नहीं मिला |
| 500 | आंतरिक सर्वर त्रुटि |
| 502 | खराब गेटवे |
प्रतिक्रिया वर्ग और तरीके
पिछले उदाहरण में उपयोग की गई "भेजें" विधियां यहां दी गई हैं:
def send_ok_response(data) Response.new(code: 200, data: data) end def send_file_not_found Response.new(code: 404) end
और यहाँ Response है कक्षा:
class Response
attr_reader :code
def initialize(code:, data: "")
@response =
"HTTP/1.1 #{code}\r\n" +
"Content-Length: #{data.size}\r\n" +
"\r\n" +
"#{data}\r\n"
@code = code
end
def send(client)
client.write(@response)
end
end
प्रतिक्रिया एक टेम्पलेट और कुछ स्ट्रिंग इंटरपोलेशन से बनाई गई है।
इस बिंदु पर हमें बस अपने कनेक्शन-स्वीकार करने वाले loop . में सब कुछ एक साथ जोड़ने की जरूरत है और फिर हमारे पास एक कार्यात्मक सर्वर होना चाहिए।
loop {
client = server.accept
request = client.readpartial(2048)
request = RequestParser.new.parse(request)
response = ResponsePreparer.new.prepare(request)
puts "#{client.peeraddr[3]} #{request.fetch(:path)} - #{response.code}"
response.send(client)
client.close
}
SERVER_ROOT . के अंतर्गत कुछ HTML फ़ाइलें जोड़ने का प्रयास करें निर्देशिका और आप उन्हें अपने ब्राउज़र से लोड करने में सक्षम होना चाहिए। यह छवियों सहित किसी भी अन्य स्थिर संपत्ति की भी सेवा करेगा।
बेशक एक वास्तविक वेब-सर्वर में कई और विशेषताएं होती हैं जिन्हें हमने यहां शामिल नहीं किया।
यहां कुछ . की सूची दी गई है गायब सुविधाओं में से, ताकि आप उन्हें एक अभ्यास के रूप में अपने दम पर लागू कर सकें (अभ्यास कौशल की जननी है!):
- वर्चुअल होस्टिंग
- माइम प्रकार
- डेटा संपीड़न
- पहुंच नियंत्रण
- मल्टी-थ्रेडिंग
- सत्यापन का अनुरोध करें
- क्वेरी स्ट्रिंग पार्सिंग
- पोस्ट बॉडी पार्सिंग
- ब्राउज़र कैशिंग (प्रतिक्रिया कोड 304)
- रीडायरेक्ट
सुरक्षा पर एक पाठ
किसी यूजर से इनपुट लेना और उसके साथ कुछ करना हमेशा खतरनाक होता है। हमारे छोटे से वेब सर्वर प्रोजेक्ट में, उपयोगकर्ता इनपुट HTTP अनुरोध है।
हमने एक छोटी सी भेद्यता पेश की है जिसे "पथ ट्रैवर्सल" के रूप में जाना जाता है। लोग किसी भी फाइल को पढ़ने में सक्षम होंगे जिस तक हमारे वेब सर्वर उपयोगकर्ता की पहुंच है, भले ही वे हमारे SERVER_ROOT से बाहर हों। निर्देशिका।
यह इस मुद्दे के लिए जिम्मेदार लाइन है:
File.binread(path)
आप इस मुद्दे को कार्रवाई में देखने के लिए स्वयं इसका फायदा उठाने का प्रयास कर सकते हैं। आपको "मैन्युअल" HTTP अनुरोध करने की आवश्यकता होगी, क्योंकि अधिकांश HTTP क्लाइंट (curl . सहित) ) आपके URL को पूर्व-संसाधित करेगा और भेद्यता को ट्रिगर करने वाले हिस्से को हटा देगा।
एक उपकरण जिसका आप उपयोग कर सकते हैं उसे नेटकैट कहा जाता है।
यहाँ एक संभावित शोषण है:
$ nc localhost 8080 GET ../../etc/passwd HTTP/1.1
यह /etc/passwd की सामग्री लौटाएगा फ़ाइल यदि आप यूनिक्स-आधारित सिस्टम पर हैं। इसके काम करने का कारण यह है कि एक डबल डॉट (.. .) ) आपको एक निर्देशिका ऊपर जाने की अनुमति देता है, इसलिए आप SERVER_ROOT से "बच" रहे हैं निर्देशिका।
एक संभावित समाधान एक में कई बिंदुओं को "संपीड़ित" करना है:
path.gsub!(/\.+/, ".")
सुरक्षा के बारे में सोचते समय हमेशा अपनी "हैकर टोपी" लगाएं और अपने समाधान को तोड़ने के तरीके खोजने का प्रयास करें। उदाहरण के लिए, यदि आपने अभी-अभी path.gsub!("..", ".") किया है , आप ट्रिपल डॉट्स (... . का उपयोग करके इसे बायपास कर सकते हैं )।
समाप्त और कार्य कोड
मुझे पता है कि इस पोस्ट में कोड हर जगह है, इसलिए यदि आप समाप्त, कार्यशील कोड की तलाश कर रहे हैं…
यहां लिंक है :
https://gist.github.com/matugm/efe0a1c4fc53310f7ac93dcd1f041f6c#file-web-server-rb
आनंद लें!
सारांश
इस पोस्ट में, आपने सीखा कि नए कनेक्शन कैसे सुनें, HTTP अनुरोध कैसा दिखता है और इसे कैसे पार्स करना है। आपने यह भी सीखा कि प्रतिक्रिया कोड और आवश्यक फ़ाइल की सामग्री (यदि उपलब्ध हो) का उपयोग करके प्रतिक्रिया कैसे बनाई जाती है।
और अंत में आपने "पाथ ट्रैवर्सल" भेद्यता और इससे बचने के तरीके के बारे में सीखा।
मुझे आशा है कि आपको यह पोस्ट अच्छी लगी होगी और कुछ नया सीखा होगा! नीचे दिए गए फॉर्म पर मेरे न्यूज़लेटर की सदस्यता लेना न भूलें, ताकि आप एक भी पोस्ट मिस न करें 🙂