কম্পিউটার

রুবিতে একটি 30 লাইন HTTP সার্ভার তৈরি করা

ওয়েব সার্ভার, এবং সাধারণভাবে HTTP, বোঝা কঠিন বলে মনে হতে পারে। কিভাবে ব্রাউজার একটি অনুরোধ ফর্ম্যাট করে, এবং কিভাবে প্রতিক্রিয়া ব্যবহারকারীর কাছে পাঠানো হয়? এই রুবি ম্যাজিক পর্বে আমরা শিখব কিভাবে একটি রুবি HTTP সার্ভার 30 লাইনের কোডে তৈরি করা যায়। আমাদের কাজ শেষ হলে, আমাদের সার্ভার HTTP GET অনুরোধগুলি পরিচালনা করবে এবং আমরা এটি একটি র্যাক অ্যাপ পরিবেশন করতে ব্যবহার করব৷

কিভাবে HTTP এবং TCP একসাথে কাজ করে

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 পোর্টে আবদ্ধ হয় এবং একটি ক্লায়েন্ট সংযোগ করার জন্য অপেক্ষা করে। যখন এটি ঘটে, এটি ক্লায়েন্টকে একটি বার্তা পাঠায় এবং তারপর সংযোগটি বন্ধ করে দেয়। প্রথম ক্লায়েন্টের সাথে কথা বলা শেষ হওয়ার পর, সার্ভার আবার তার বার্তা পাঠাতে অন্য ক্লায়েন্টের সংযোগের জন্য অপেক্ষা করে।

# 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
$

কিছুটা কল্পনার সাথে, আমাদের TCP সার্ভার এবং ক্লায়েন্ট কিছুটা ওয়েব সার্ভার এবং একটি ব্রাউজারের মতো কাজ করে। ক্লায়েন্ট একটি অনুরোধ পাঠায়, সার্ভার সাড়া দেয় এবং সংযোগ বন্ধ হয়ে যায়। এইভাবে অনুরোধ-প্রতিক্রিয়া প্যাটার্ন কাজ করে, যা আমাদের একটি HTTP সার্ভার তৈরি করতে হবে।

আমরা ভাল অংশে যাওয়ার আগে, আসুন HTTP অনুরোধ এবং প্রতিক্রিয়াগুলি কেমন তা দেখা যাক৷

একটি মৌলিক HTTP GET অনুরোধ

সবচেয়ে মৌলিক HTTP GET অনুরোধ হল একটি রিকোয়েস্ট-লাইন কোনো অতিরিক্ত হেডার বা রিকোয়েস্ট বডি ছাড়াই।

GET / HTTP/1.1\r\n

অনুরোধ-লাইন চারটি অংশ নিয়ে গঠিত:

  • একটি পদ্ধতির টোকেন (GET , এই উদাহরণে)
  • রিকোয়েস্ট-ইউআরআই (/ )
  • প্রটোকল সংস্করণ (HTTP/1.1 )
  • একটি সিআরএলএফ (একটি ক্যারেজ রিটার্ন:\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) দিয়ে শেষ করা হয়েছে। )
  • ঐচ্ছিক হেডার লাইন। এই ক্ষেত্রে, শুধুমাত্র একটি হেডার লাইন আছে ("কন্টেন্ট-টাইপ:টেক্সট/এইচটিএমএল"), কিন্তু একাধিক হতে পারে (একটি CRLF দিয়ে আলাদা করা হয়েছে:\r\n )
  • বডি থেকে স্ট্যাটাস লাইন এবং হেডার আলাদা করার জন্য একটি নতুন লাইন (বা একটি ডবল CRLF):(\r\n\r\n )
  • শরীর:"হ্যালো ওয়ার্ল্ড!"

একটি ন্যূনতম রুবি HTTP সার্ভার

যথেষ্ট কথা। এখন যেহেতু আমরা জানি কিভাবে রুবিতে একটি TCP সার্ভার তৈরি করতে হয় এবং কিছু 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 ব্যবহার করে ক্লায়েন্টকে একটি বার্তা ফেরত পাঠাতে:শুধুমাত্র আমাদের বার্তার পরিবর্তে, এটি একটি স্ট্যাটাস লাইন, একটি শিরোনাম এবং একটি নতুন লাইন সহ প্রতিক্রিয়ার উপসর্গ দেয়:

  1. স্থিতি লাইন (HTTP 1.1 200\r\n ) ব্রাউজারকে জানাতে যে HTTP সংস্করণটি 1.1 এবং প্রতিক্রিয়া কোড হল "200"
  2. একটি শিরোনাম নির্দেশ করে যে প্রতিক্রিয়াটিতে একটি পাঠ্য/এইচটিএমএল সামগ্রীর ধরন রয়েছে (Content-Type: text/html\r\n )
  3. নতুন লাইন (\r\n )
  4. শরীর:"হ্যালো ওয়ার্ল্ড! …"

আগের মতো, এটি বার্তা পাঠানোর পরে সংযোগ বন্ধ করে দেয়। আমরা এখনও অনুরোধটি পড়িনি, তাই এটি আপাতত কনসোলে প্রিন্ট করে৷

আপনি যদি সার্ভার চালু করেন এবং আপনার ব্রাউজারে https://localhost:5678 খোলেন, তাহলে আপনি "হ্যালো ওয়ার্ল্ড! …"-বর্তমান সময়ের সাথে লাইন দেখতে পাবেন, যেমনটি আমরা আগে আমাদের TCP ক্লায়েন্ট থেকে পেয়েছি। 🎉

একটি র্যাক অ্যাপ পরিবেশন করা হচ্ছে

এখন পর্যন্ত, আমাদের সার্ভার প্রতিটি অনুরোধের জন্য একটি একক প্রতিক্রিয়া ফিরিয়ে দিচ্ছে। এটিকে একটু বেশি উপযোগী করতে, আমরা আমাদের সার্ভারে আরও প্রতিক্রিয়া যোগ করতে পারি। এগুলি সরাসরি সার্ভারে যোগ করার পরিবর্তে, আমরা একটি র্যাক অ্যাপ ব্যবহার করব। আমাদের সার্ভার HTTP অনুরোধগুলি পার্স করবে এবং সেগুলিকে র্যাক অ্যাপে পাঠাবে, যা ক্লায়েন্টকে ফেরত পাঠানোর জন্য সার্ভারের জন্য একটি প্রতিক্রিয়া ফিরিয়ে দেবে৷

র্যাক হল ওয়েব সার্ভারের মধ্যে একটি ইন্টারফেস যা রুবি এবং বেশিরভাগ রুবি ওয়েব ফ্রেমওয়ার্ক যেমন রেল এবং সিনাট্রাকে সমর্থন করে। এর সহজতম আকারে, একটি র্যাক অ্যাপ হল একটি বস্তু যা call-এ সাড়া দেয় এবং একটি "টিপলেট", তিনটি আইটেম সহ একটি অ্যারে প্রদান করে:একটি HTTP প্রতিক্রিয়া কোড, HTTP হেডারগুলির একটি হ্যাশ এবং একটি বডি৷

app = Proc.new do |env|
  ['200', {'Content-Type' => 'text/html'}, ["Hello world! The time is #{Time.now}"]]
end

এই উদাহরণে, রেসপন্স কোড হল "200", আমরা হেডারের মাধ্যমে কন্টেন্ট টাইপ হিসাবে "text/html" পাস করছি, এবং বডি হল একটি স্ট্রিং সহ একটি অ্যারে৷

আমাদের সার্ভারকে এই অ্যাপ থেকে প্রতিক্রিয়া পরিবেশনের অনুমতি দিতে, আমাদের প্রত্যাবর্তিত ট্রিপলেটটিকে একটি 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

র্যাক অ্যাপ থেকে আমরা যে প্রতিক্রিয়া পেয়েছি তা পরিবেশন করার জন্য, আমরা আমাদের সার্ভারে কিছু পরিবর্তন করব:

  1. app.call দ্বারা ফেরত দেওয়া ট্রিপলেট থেকে স্ট্যাটাস কোড, হেডার এবং বডি পান .
  2. স্ট্যাটাস লাইন তৈরি করতে স্ট্যাটাস কোড ব্যবহার করুন
  3. হেডারের উপর লুপ করুন এবং হ্যাশে প্রতিটি কী-মান জোড়ার জন্য একটি হেডার লাইন যোগ করুন
  4. বডি থেকে স্ট্যাটাস লাইন এবং হেডার আলাদা করতে একটি নতুন লাইন প্রিন্ট করুন
  5. শরীরের উপর লুপ করুন এবং প্রতিটি অংশ প্রিন্ট করুন। যেহেতু আমাদের বডি অ্যারেতে শুধুমাত্র একটি অংশ আছে, তাই এটি বন্ধ করার আগে সেশনে আমাদের "হ্যালো ওয়ার্ল্ড"-মেসেজটি প্রিন্ট করবে।

রিকুয়েস্ট পড়া

এখন পর্যন্ত, আমাদের সার্ভার request উপেক্ষা করছে পরিবর্তনশীল আমাদের প্রয়োজন ছিল না কারণ আমাদের র্যাক অ্যাপ সবসময় একই প্রতিক্রিয়া ফেরত দেয়।

Rack::Lobster একটি উদাহরণ অ্যাপ যা র্যাকের সাথে পাঠানো হয় এবং কাজ করার জন্য অনুরোধ URL প্যারামিটার ব্যবহার করে। আমরা আগে একটি অ্যাপ হিসেবে ব্যবহার করতাম Proc-এর পরিবর্তে, এখন থেকে আমরা সেটিকে আমাদের টেস্টিং অ্যাপ হিসেবে ব্যবহার করব।

# 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 যথাক্রমে যাইহোক, লিঙ্কগুলি অনুসরণ করার সময়, গলদা চিংড়িটি উল্টে যায় না এবং এখনও কিছুই ক্র্যাশ হয় না। কারণ আমাদের সার্ভার এই মুহূর্তে কোয়েরি স্ট্রিংগুলি পরিচালনা করে না৷ request মনে রাখবেন পরিবর্তনশীল আমরা আগে উপেক্ষা? আমরা যদি আমাদের সার্ভারের লগগুলি দেখি, আমরা প্রতিটি পৃষ্ঠার জন্য অনুরোধের স্ট্রিংগুলি দেখতে পাব৷

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

অনুরোধটি পার্স করতে এবং অনুরোধের প্যারামিটারগুলি র্যাক অ্যাপে পাঠাতে, আমরা অনুরোধের স্ট্রিংকে বিভক্ত করব এবং র্যাক অ্যাপে পাঠাব:

  1. অনুরোধের স্ট্রিংটিকে একটি পদ্ধতি এবং একটি সম্পূর্ণ পাথে বিভক্ত করুন
  2. সম্পূর্ণ পথটিকে একটি পাথ এবং একটি প্রশ্নে বিভক্ত করুন
  3. একটি র্যাক এনভায়রনমেন্ট হ্যাশে সেগুলি আমাদের অ্যাপে পাঠান৷

উদাহরণস্বরূপ, একটি অনুরোধ যেমন GET /?flip=left HTTP/1.1\r\n এইভাবে অ্যাপে পাস করা হবে:

{
  'REQUEST_METHOD' => 'GET',
  'PATH_INFO' => '/',
  'QUERY_STRING' => '?flip=left'
}

আমাদের সার্ভার রিস্টার্ট করে, https://localhost:5678 এ গিয়ে এবং "flip!"-লিংকে ক্লিক করলে এখন গলদা চিংড়ি ফ্লিপ হবে এবং "ক্র্যাশ!" এ ক্লিক করলে লিঙ্ক আমাদের ওয়েব সার্ভার ক্র্যাশ করবে৷

আমরা সবেমাত্র একটি HTTP সার্ভার বাস্তবায়নের পৃষ্ঠটি স্ক্র্যাচ করেছি, এবং আমাদের কোডের মাত্র 30 লাইন, কিন্তু এটি মৌলিক ধারণা ব্যাখ্যা করে। এটি GET অনুরোধগুলি গ্রহণ করে, একটি Rack অ্যাপে অনুরোধের বৈশিষ্ট্যগুলি পাস করে এবং ব্রাউজারে প্রতিক্রিয়াগুলি ফেরত পাঠায়৷ যদিও এটি অনুরোধ স্ট্রিমিং এবং পোস্ট অনুরোধের মতো জিনিসগুলি পরিচালনা করে না, আমাদের সার্ভারটি তাত্ত্বিকভাবে অন্যান্য র্যাক অ্যাপগুলিকেও পরিবেশন করতে ব্যবহার করা যেতে পারে৷

এটি রুবিতে একটি এইচটিটিপি সার্ভার তৈরি করার বিষয়ে আমাদের দ্রুত দৃষ্টিভঙ্গি শেষ করে। আপনি যদি আমাদের সার্ভারের সাথে খেলতে চান তবে কোডটি এখানে। আপনি যদি আরও জানতে চান বা কোনো নির্দিষ্ট প্রশ্ন করতে চান তাহলে @AppSignal-এ আমাদের জানান।

আপনি যদি এই নিবন্ধটি উপভোগ করেন তবে রুবি ম্যাজিক নিউজলেটারে সদস্যতা নিন:রুবির একটি (মোটামুটি) মাসিক po(r)tion৷


  1. রুবিতে একটি খেলনা প্রোগ্রামিং ভাষা তৈরি করা

  2. রুবিতে একটি প্রোগ্রামিং ভাষা তৈরি করা:দ্য ইন্টারপ্রেটার, পার্ট 2

  3. রুবিতে একটি নতুন প্রোগ্রামিং ভাষা তৈরি করা:দোভাষী

  4. কেন আমাদের রুবিতে অ্যাপ্লিকেশন সার্ভারের প্রয়োজন? (পুমার মত)