আপনি কি কখনও রুবি দিয়ে নিজের ওয়েব সার্ভার তৈরি করেছেন?
আমাদের ইতিমধ্যে অনেক সার্ভার আছে, যেমন:
- পুমা
- পাতলা
- ইউনিকর্ন
কিন্তু আমি মনে করি এটি একটি দুর্দান্ত শেখার ব্যায়াম যদি আপনি জানতে চান কিভাবে একটি সাধারণ ওয়েব সার্ভার কাজ করে।
এই নিবন্ধে, আপনি কীভাবে এটি করবেন তা শিখবেন।
ধাপে ধাপে!
ধাপ 1:সংযোগের জন্য শোনা
আমরা কোথায় শুরু করব?
টিসিপি পোর্ট 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:অনুরোধ পার্সিং
এখন আমাদের অনুরোধটিকে ছোট ছোট অংশে ভাগ করতে হবে যা আমাদের সার্ভার বুঝতে পারে৷
এটি করতে আমরা আমাদের নিজস্ব পার্সার তৈরি করতে পারি বা ইতিমধ্যে বিদ্যমান একটি ব্যবহার করতে পারি। আমরা আমাদের নিজস্ব নির্মাণ করতে যাচ্ছি তাই অনুরোধের বিভিন্ন অংশের অর্থ কী তা আমাদের বুঝতে হবে।
এই চিত্রটি সাহায্য করবে৷ :
অনুরোধ পান
হেডারগুলি ব্রাউজার ক্যাশিং, ভার্চুয়াল হোস্টিং এবং ডেটা কম্প্রেশনের মতো জিনিসগুলির জন্য ব্যবহার করা হয়, কিন্তু একটি মৌলিক বাস্তবায়নের জন্য আমরা সেগুলিকে উপেক্ষা করতে পারি এবং এখনও একটি কার্যকরী সার্ভার থাকতে পারি৷
একটি সাধারণ 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 প্রতিক্রিয়া কোডের সারণী
রেফারেন্সের জন্য।
| কোড | বর্ণনা |
|---|---|
| 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 সহ) ).
একটি টুল যা আপনি ব্যবহার করতে পারেন তা হল নেটক্যাট৷
৷এখানে একটি সম্ভাব্য শোষণ আছে:
$ 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 অনুরোধ কেমন দেখায় এবং কীভাবে এটি পার্স করতে হয়। আপনি একটি প্রতিক্রিয়া কোড এবং প্রয়োজনীয় ফাইলের বিষয়বস্তু (যদি উপলব্ধ) ব্যবহার করে প্রতিক্রিয়া তৈরি করতে হয় তাও শিখেছেন।
এবং অবশেষে আপনি "পাথ ট্রাভার্সাল" দুর্বলতা এবং কীভাবে এটি এড়ানো যায় সে সম্পর্কে শিখেছেন৷
আমি আশা করি আপনি এই পোস্টটি উপভোগ করেছেন এবং নতুন কিছু শিখেছেন! নীচের ফর্মে আমার নিউজলেটার সাবস্ক্রাইব করতে ভুলবেন না, যাতে আপনি একটি পোস্ট মিস করবেন না 🙂