क्या आपने कभी रूबी के साथ अपना खुद का वेब सर्वर बनाया है?
हमारे पास पहले से ही कई सर्वर हैं, जैसे:
- Puma
- Thin
- Unicorn
लेकिन मुझे लगता है कि यह एक बेहतरीन सीखने का अभ्यास है यदि आप जानना चाहते हैं कि एक साधारण वेब सर्वर कैसे काम करता है।
इस लेख में, आप सीखेंगे कि यह कैसे करना है।
चरण-दर-चरण!
चरण 1: कनेक्शन के लिए सुनना
हम कहां से शुरू करें?
पहली चीज जिसकी हमें आवश्यकता है वह है TCP पोर्ट 80 पर नए कनेक्शन के लिए सुनना।
मैं पहले ही रूबी में नेटवर्क प्रोग्रामिंग के बारे में एक पोस्ट लिख चुका हूं, इसलिए मैं यहां यह नहीं बताऊंगा कि यह कैसे काम करता है।
मैं सिर्फ आपको कोड देने जा रहा हूं:
require 'socket' server = TCPServer.new('localhost', 80) loop { client = server.accept request = client.readpartial(2048) puts request } जब आप इस कोड को चलाते हैं तो आपके पास एक सर्वर होगा जो पोर्ट 80 पर कनेक्शन स्वीकार करता है। यह अभी बहुत कुछ नहीं करता है, लेकिन यह आपको यह देखने की अनुमति देगा कि एक इनकमिंग रिक्वेस्ट कैसी दिखती है।
नोट: लिनक्स/मैक सिस्टम में पोर्ट 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: रिक्वेस्ट को पार्स करना
अब हमें रिक्वेस्ट को छोटे घटकों में तोड़ने की आवश्यकता है जिन्हें हमारा सर्वर समझ सके।
ऐसा करने के लिए हम अपना खुद का पार्सर बना सकते हैं या पहले से मौजूद किसी एक का उपयोग कर सकते हैं। हम अपना खुद का बनाने जा रहे हैं इसलिए हमें यह समझने की आवश्यकता है कि रिक्वेस्ट के विभिन्न भागों का क्या अर्थ है।
यह छवि मदद करनी चाहिए:

GET Request
हेडर्स का उपयोग ब्राउज़र कैशिंग, वर्चुअल होस्टिंग और डेटा कम्प्रेशन जैसी चीजों के लिए किया जाता है, लेकिन एक बुनियादी कार्यान्वयन के लिए हम उन्हें अनदेखा कर सकते हैं और फिर भी एक कार्यात्मक सर्वर रख सकते हैं।
एक साधारण 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है। - दूसरा, यदि अनुरोधित फाइल मिल जाती है, तो हम फाइल की सामग्री को OK रिस्पॉन्स के साथ भेजने जा रहे हैं।
लेकिन अगर फाइल नहीं मिलती है तो हम विशिष्ट 404 Not Found रिस्पॉन्स भेजने जा रहे हैं।
सबसे आम HTTP रिस्पॉन्स कोड की तालिका
संदर्भ के लिए।
| कोड | विवरण |
|---|---|
| 200 | OK |
| 301 | Moved permanently |
| 302 | Found |
| 304 | Not Modified |
| 400 | Bad Request |
| 401 | Unauthorized |
| 403 | Forbidden |
| 404 | Not found |
| 500 | Internal Server Error |
| 502 | Bad Gateway |
रिस्पॉन्स क्लास और मेथड्स
यहां पिछले उदाहरण में उपयोग की जाने वाली "send" मेथड्स हैं:
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 फाइल्स जोड़ने का प्रयास करें और आप उन्हें अपने ब्राउज़र से लोड करने में सक्षम होना चाहिए। यह इमेजेज सहित किसी भी अन्य स्टेटिक एसेट्स को भी सर्व करेगा।
बेशक एक असली वेब-सर्वर में कई और फीचर्स हैं जिन्हें हमने यहां कवर नहीं किया।
यहां कुछ गायब फीचर्स की सूची है, ताकि आप उन्हें अभ्यास के रूप में स्वयं लागू कर सकें (अभ्यास ही कौशल की जननी है!):
- वर्चुअल होस्टिंग
- Mime टाइप्स
- डेटा कम्प्रेशन
- एक्सेस कंट्रोल
- मल्टी-थ्रेडिंग
- रिक्वेस्ट वैलिडेशन
- क्वेरी स्ट्रिंग पार्सिंग
- POST बॉडी पार्सिंग
- ब्राउज़र कैशिंग (रिस्पॉन्स कोड 304)
- रीडायरेक्ट्स
सुरक्षा पर एक सबक
उपयोगकर्ता से इनपुट लेना और उसके साथ कुछ करना हमेशा खतरनाक होता है। हमारे छोटे वेब सर्वर प्रोजेक्ट में, उपयोगकर्ता इनपुट HTTP रिक्वेस्ट है।
हमने "पाथ ट्रैवर्सल" के रूप में जानी जाने वाली एक छोटी सी भेद्यता पेश की है। लोग उन सभी फाइलों को पढ़ने में सक्षम होंगे जिन तक हमारे वेब सर्वर उपयोगकर्ता की पहुंच है, भले ही वे हमारे SERVER_ROOT डायरेक्टरी के बाहर हों।
यह इस समस्या के लिए जिम्मेदार लाइन है:
File.binread(path)
आप इस समस्या का स्वयं फायदा उठाकर इसे एक्शन में देख सकते हैं। आपको एक "मैनुअल" HTTP रिक्वेस्ट बनाने की आवश्यकता होगी, क्योंकि अधिकांश HTTP क्लाइंट ( curl सहित) आपके URL को प्री-प्रोसेस करेंगे और उस हिस्से को हटा देंगे जो भेद्यता को ट्रिगर करता है।
आप जिस टूल का उपयोग कर सकते हैं उसे netcat कहा जाता है।
यहां एक संभावित शोषण है:
$ 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 रिक्वेस्ट कैसी दिखती है और इसे कैसे पार्स करें। आपने यह भी सीखा कि रिस्पॉन्स कोड और आवश्यक फाइल की सामग्री (यदि उपलब्ध हो) का उपयोग करके रिस्पॉन्स कैसे बनाया जाए।
और अंत में आपने "पाथ ट्रैवर्सल" भेद्यता और इससे कैसे बचा जाए, इसके बारे में सीखा।
मुझे आशा है कि आपको यह पोस्ट पसंद आई और आपने कुछ नया सीखा! नीचे दिए गए फॉर्म पर मेरे न्यूज़लेटर को सब्सक्राइब करना न भूलें, ताकि आप एक भी पोस्ट मिस न करें 🙂