রুবি ম্যাজিক সিরিজে আমরা সফ্টওয়্যারটিকে হুডের নিচে কীভাবে কাজ করে তা শিখতে আলাদা করতে পছন্দ করি। এটা সব প্রক্রিয়া সম্পর্কে; শেষ ফলাফল এমন কিছু নয় যা আপনি উৎপাদনে ব্যবহার করবেন, আমরা রুবি ভাষার অভ্যন্তরীণ কাজ এবং এর জনপ্রিয় লাইব্রেরি সম্পর্কে শিখি। আমরা মাসে প্রায় একবার একটি নতুন নিবন্ধ প্রকাশ করি, তাই আপনি যদি এই ধরণের জিনিসটি নিয়ে থাকেন তবে আমাদের নিউজলেটারে সদস্যতা নিতে ভুলবেন না৷
রুবি ম্যাজিকের পূর্ববর্তী সংস্করণে আমরা রুবিতে একটি 30-লাইন HTTP সার্ভার প্রয়োগ করেছি। প্রচুর কোড না লিখে, আমরা HTTP GET অনুরোধগুলি পরিচালনা করতে এবং একটি সাধারণ র্যাক অ্যাপ্লিকেশন পরিবেশন করতে সক্ষম হয়েছি। এইবার, আমরা আমাদের বাড়িতে তৈরি সার্ভারকে একটু এগিয়ে নিয়ে যাব। আমাদের কাজ শেষ হলে, আমাদের কাছে একটি ওয়েব সার্ভার থাকবে যা রেলের বিখ্যাত পনের মিনিটের ব্লগ পরিবেশন করতে পারে যা আপনাকে পোস্টগুলি তৈরি করতে, আপডেট করতে এবং মুছতে দেয়৷
যেখানে আমরা ছেড়ে এসেছি
গতবার, আমরা একটি সার্ভার প্রয়োগ করেছি যাতে এটি একটি উদাহরণ অ্যাপ্লিকেশন হিসাবে Rack::Lobster পরিবেশন করে।
- আমাদের বাস্তবায়ন একটি TCP সার্ভার খুলেছে এবং একটি অনুরোধ আসার জন্য অপেক্ষা করছে।
- যখন এটি ঘটেছিল, অনুরোধ-লাইন (
GET /?flip=left HTTP/1.1\r\n
অনুরোধের পদ্ধতি (GET
) পেতে পার্স করা হয়েছে ), পথ (/
), এবং ক্যোয়ারী প্যারামিটার (flip=left
)। - অনুরোধের পদ্ধতি, পথ এবং ক্যোয়ারী স্ট্রিংটি র্যাক অ্যাপে পাঠানো হয়েছে, যা একটি স্ট্যাটাস, কিছু প্রতিক্রিয়া শিরোনাম এবং প্রতিক্রিয়া বডি সহ একটি ট্রিপলেট ফিরিয়ে দিয়েছে।
- সেগুলি ব্যবহার করে, আমরা ব্রাউজারে ফেরত পাঠানোর জন্য একটি HTTP প্রতিক্রিয়া তৈরি করতে সক্ষম হয়েছি, একটি নতুন অনুরোধ আসার জন্য অপেক্ষা করার জন্য সংযোগটি বন্ধ করার আগে৷
# http_server.rb
require 'socket'
require 'rack'
require 'rack/lobster'
app = Rack::Lobster.new
server = TCPServer.new 5678
#1
while session = server.accept
request = session.gets
puts request
#2
method, full_path = request.split(' ')
path, query = full_path.split('?')
#3
status, headers, body = app.call({
'REQUEST_METHOD' => method,
'PATH_INFO' => path,
'QUERY_STRING' => query
})
#4
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
আমরা গতবার যে কোডটি লিখেছিলাম তার সাথে আমরা চালিয়ে যাব। আপনি যদি অনুসরণ করতে চান, তাহলে এই কোডটি আমরা শেষ করেছি৷
৷র্যাক এবং রেল
রেল এবং সিনাত্রার মতো রুবি ফ্রেমওয়ার্কগুলি র্যাক ইন্টারফেসের উপরে তৈরি করা হয়েছে। ঠিক যেমন Rack::Lobster
এর উদাহরণ আমরা এখন আমাদের সার্ভার পরীক্ষা করার জন্য ব্যবহার করছি, Rails' Rails.application
একটি র্যাক অ্যাপ্লিকেশন অবজেক্ট। তত্ত্বগতভাবে, এর অর্থ হল আমাদের সার্ভার ইতিমধ্যেই একটি রেল অ্যাপ্লিকেশন পরিবেশন করতে সক্ষম হওয়া উচিত।
এটি পরীক্ষা করার জন্য, আমি একটি সাধারণ রেল অ্যাপ্লিকেশন প্রস্তুত করেছি। আসুন এটিকে আমাদের সার্ভারের মতো একই ডিরেক্টরিতে ক্লোন করি।
$ ls
http_server.rb
$ git clone https://github.com/jeffkreeftmeijer/wups.git blog
Cloning into 'blog'...
remote: Counting objects: 162, done.
remote: Compressing objects: 100% (112/112), done.
remote: Total 162 (delta 32), reused 162 (delta 32), pack-reused 0
Receiving objects: 100% (162/162), 29.09 KiB | 0 bytes/s, done.
Resolving deltas: 100% (32/32), done.
Checking connectivity... done.
$ ls
blog http_server.rb
তারপর, আমাদের সার্ভারে, rack
এর পরিবর্তে Rails অ্যাপ্লিকেশনের পরিবেশ ফাইল প্রয়োজন এবং rack/lobster
, এবং Rails.application
বসান app
-এ Rack::Lobster.new
এর পরিবর্তে পরিবর্তনশীল .
# http_server.rb
require 'socket'
require_relative 'blog/config/environment'
app = Rails.application
server = TCPServer.new 5678
# ...
সার্ভার শুরু করা হচ্ছে (ruby http_server.rb
) এবং https://localhost:5678 খোলা আমাদের দেখায় যে আমরা এখনও সেখানে নেই। সার্ভারটি ক্র্যাশ হয় না, তবে ব্রাউজারে একটি অভ্যন্তরীণ সার্ভার ত্রুটির সাথে আমরা স্বাগত জানাই৷
আমাদের সার্ভারের লগ পরীক্ষা করে আমরা দেখতে পাচ্ছি যে আমরা rack.input
নামক কিছু মিস করছি . দেখা যাচ্ছে যে গতবার আমাদের সার্ভার বাস্তবায়ন করার সময় আমরা অলস ছিলাম, তাই এই রেল অ্যাপ্লিকেশনটি কাজ করার আগে আমাদের আরও কাজ করতে হবে৷
$ ruby http_server.rb
GET / HTTP/1.1
Error during failsafe response: Missing rack.input
...
http_server.rb:15:in `<main>'
র্যাক পরিবেশ
যখন আমরা আমাদের সার্ভারটি প্রয়োগ করেছি, তখন আমরা র্যাক পরিবেশের উপর আলোকপাত করেছি এবং র্যাক অ্যাপ্লিকেশনগুলিকে সঠিকভাবে পরিবেশন করার জন্য প্রয়োজনীয় বেশিরভাগ ভেরিয়েবলকে উপেক্ষা করেছি। আমরা শুধুমাত্র REQUEST_METHOD
বাস্তবায়ন করে শেষ করেছি , PATH_INFO
, এবং QUERY_STRING
ভেরিয়েবল, যেগুলি আমাদের সাধারণ র্যাক অ্যাপের জন্য যথেষ্ট ছিল৷
যেমনটি আমরা ইতিমধ্যে ব্যতিক্রম থেকে দেখেছি যখন আমরা আমাদের নতুন অ্যাপ্লিকেশন শুরু করার চেষ্টা করেছি, রেলের প্রয়োজন rack.input
, যা কাঁচা HTTP POST ডেটার জন্য একটি ইনপুট স্ট্রীম হিসাবে ব্যবহৃত হয়। এর পাশাপাশি, সার্ভারের পোর্ট নম্বর এবং অনুরোধ কুকি ডেটার মতো আরও কিছু ভেরিয়েবল আমাদের পাস করতে হবে৷
ভাগ্যক্রমে, Rack Rack::Lint
প্রদান করে র্যাক পরিবেশে সমস্ত ভেরিয়েবল উপস্থিত এবং বৈধ তা নিশ্চিত করতে সহায়তা করতে। আমরা Rack::Lint.new
এ কল করে আমাদের Rails অ্যাপটি এতে মুড়ে আমাদের সার্ভার পরীক্ষা করতে এটি ব্যবহার করতে পারি এবং Rails.application
পাস করা .
# http_server.rb
require 'socket'
require_relative 'blog/config/environment'
app = Rack::Lint.new(Rails.application)
server = TCPServer.new 5678
# ...
Rack::Lint
পরিবেশে একটি পরিবর্তনশীল অনুপস্থিত বা অবৈধ হলে একটি ব্যতিক্রম নিক্ষেপ করবে। এই মুহূর্তে, আমাদের সার্ভার আবার শুরু করা এবং https://localhost:5678 খুললে সার্ভার ক্র্যাশ হবে এবং Rack::Lint
প্রথম ত্রুটি সম্পর্কে আমাদের অবহিত করবে:SERVER_NAME
পরিবর্তনশীল সেট করা হয়নি।
~/Appsignal/http-server (master) $ ruby http_server.rb
GET / HTTP/1.1
/Users/jeff/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/lint.rb:20:in `assert': env missing required key SERVER_NAME (Rack::Lint::LintError)
...
from http_server.rb:15:in `<main>'
আমাদের দিকে ছুড়ে দেওয়া প্রতিটি ত্রুটি ঠিক করে, আমরা Rack::Lint
পর্যন্ত ভেরিয়েবল যোগ করতে পারি। আমাদের সার্ভার ক্র্যাশ করা বন্ধ করে। চলুন চলুন প্রতিটি ভেরিয়েবলের উপর যাই Rack::Lint
প্রয়োজন।
SERVER_NAME
:সার্ভারের হোস্টনাম। আমরা এই মুহূর্তে শুধুমাত্র স্থানীয়ভাবে এই সার্ভারটি চালাচ্ছি, তাই আমরা "localhost" ব্যবহার করব।SERVER_PORT
:আমাদের সার্ভার যে পোর্টে চলছে। আমরা পোর্ট নম্বর (5678) হার্ডকোড করেছি, তাই আমরা এটিকে র্যাক পরিবেশে পাস করব৷rack.version
:লক্ষ্যযুক্ত র্যাক প্রটোকল পূর্ণসংখ্যার একটি অ্যারে হিসাবে সংস্করণ সংখ্যা।[1,3]
লেখার সময়।rack.input
:কাঁচা HTTP পোস্ট ডেটা ধারণকারী ইনপুট স্ট্রীম। আমরা পরে এটিতে যাব, কিন্তু আমরা একটি খালিStringIO
পাস করব উদাহরণ (একটি ASCII-8BIT এনকোডিং সহ) আপাতত৷rack.errors
:Rack::Logger
এর জন্য ত্রুটি স্ট্রীম লিখতে আমরা$stderr
ব্যবহার করছি .rack.multithread
:আমাদের সার্ভার একক-থ্রেডেড, তাই এটিfalse
এ সেট করা যেতে পারে .rack.multiprocess
:আমাদের সার্ভার একটি একক প্রক্রিয়ায় চলছে, তাই এটিfalse
এ সেট করা যেতে পারে সেইসাথে।rack.run_once
:আমাদের সার্ভার এক প্রক্রিয়ায় একাধিক অনুক্রমিক অনুরোধ পরিচালনা করতে পারে, তাই এটিfalse
এছাড়াও।rack.url_scheme
:কোন SSL সমর্থন নেই, তাই এটি "https" এর পরিবর্তে "http" সেট করা যেতে পারে।
সমস্ত অনুপস্থিত ভেরিয়েবল যোগ করার পরে, Rack::Lint
আমাদের পরিবেশে আরও একটি সমস্যা সম্পর্কে আমাদের অবহিত করবে।
$ ruby http_server.rb
GET / HTTP/1.1
/Users/jeff/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/rack-2.0.1/lib/rack/lint.rb:20:in `assert': env variable QUERY_STRING has non-string value nil (Rack::Lint::LintError)
...
from http_server.rb:18:in `<main>'
যখন অনুরোধে কোনো ক্যোয়ারী স্ট্রিং না থাকে, আমরা এখন nil
পাস করব QUERY_STRING
হিসাবে , যা অনুমোদিত নয়। সেই ক্ষেত্রে, রাক পরিবর্তে একটি খালি স্ট্রিং আশা করে। অনুপস্থিত ভেরিয়েবলগুলি বাস্তবায়ন এবং ক্যোয়ারী স্ট্রিং আপডেট করার পরে, আমাদের পরিবেশটি এইরকম দেখায়:
# http_server.rb
# ...
method, full_path = request.split(' ')
path, query = full_path.split('?')
input = StringIO.new
input.set_encoding 'ASCII-8BIT'
status, headers, body = app.call({
'REQUEST_METHOD' => method,
'PATH_INFO' => path,
'QUERY_STRING' => query || '',
'SERVER_NAME' => 'localhost',
'SERVER_PORT' => '5678',
'rack.version' => [1,3],
'rack.input' => input,
'rack.errors' => $stderr,
'rack.multithread' => false,
'rack.multiprocess' => false,
'rack.run_once' => false,
'rack.url_scheme' => 'http'
})
session.print "HTTP/1.1 #{status}\r\n"
# ...
সার্ভারটি পুনরায় চালু করা এবং আবার https://localhost:5678-এ যাওয়া, আমাদেরকে Rails' "You are on Rails!"-পৃষ্ঠার সাথে স্বাগত জানানো হবে, যার অর্থ আমরা এখন আমাদের বাড়িতে তৈরি সার্ভারে একটি প্রকৃত রেল অ্যাপ্লিকেশন চালাচ্ছি!
HTTP POST বডি পার্স করা হচ্ছে
এই অ্যাপ্লিকেশনটি কেবলমাত্র সেই সূচী পৃষ্ঠার চেয়ে বেশি। https://localhost:5678/posts ভিজিট করলে পোস্টের একটি খালি তালিকা দেখাবে। আমরা যদি নতুন পোস্ট ফর্মটি পূরণ করে এবং "পোস্ট তৈরি করুন" টিপে একটি নতুন পোস্ট তৈরি করার চেষ্টা করি, তাহলে আমাদের একটি ActionController::InvalidAuthenticityToken
দ্বারা স্বাগত জানানো হবে। ব্যতিক্রম।
একটি ফর্ম পোস্ট করার সময় সত্যতা টোকেনটি পাঠানো হয় এবং অনুরোধটি বিশ্বস্ত উত্স থেকে এসেছে কিনা তা পরীক্ষা করতে ব্যবহৃত হয়। আমাদের সার্ভার এখন সম্পূর্ণরূপে POST ডেটা উপেক্ষা করছে, তাই টোকেন পাঠানো হয় না, এবং অনুরোধটি যাচাই করা যায় না৷
আমরা যখন প্রথম আমাদের HTTP সার্ভার প্রয়োগ করেছি, তখন আমরা session.gets
ব্যবহার করেছি প্রথম লাইন পেতে (অনুরোধ-লাইন বলা হয়), এবং সেই থেকে HTTP পদ্ধতি এবং পথ পার্স করা। অনুরোধ-লাইন পার্স করার পাশাপাশি, আমরা বাকি অনুরোধ উপেক্ষা করেছি।
POST ডেটা বের করতে সক্ষম হওয়ার জন্য, আমাদের প্রথমে বুঝতে হবে কিভাবে একটি HTTP অনুরোধ গঠন করা হয়। একটি উদাহরণের দিকে তাকালে, আমরা দেখতে পাচ্ছি যে কাঠামোটি একটি HTTP প্রতিক্রিয়ার অনুরূপ:
POST /posts HTTP/1.1\r\n
Host: localhost:5678\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Encoding: gzip, deflate\r\n
Accept-Language: en-us\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Origin: https://localhost:5678\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14\r\n
Cookie: _wups_session=LzE0Z2hSZFNseG5TR3dEVEwzNE52U0lFa0pmVGlQZGtZR3AveWlyMEFvUHRPeXlQUzQ4L0xlKzNLVWtqYld2cjdiWkpmclZIaEhJd1R6eDhaZThFbVBlN2p6QWpJdllHL2F4Z3VseUZ6NU1BRTU5Y1crM2lLRVY0UzdSZkpwYkt2SGFLZUQrYVFvaFE0VjZmZlIrNk5BPT0tLUpLTHQvRHQ0T3FycWV0ZFZhVHZWZkE9PQ%3D%3D--4ef4508c936004db748da10be58731049fa190ee\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
Referer: https://localhost:5678/posts/new\r\n
Content-Length: 369\r\n
\r\n
utf8=%E2%9C%93&authenticity_token=3fu7e8v70K0h9o%2FGNiXxaXSVg3nZ%2FuoL60nlhssUEHpQRz%2BM4ZIHjQduQMexvXrNoC2pjmhNPI4xNNA0Qkh5Lg%3D%3D&post%5Btitle%5D=My+first+post&post%5Bcreated_at%281i%29%5D=2017&post%5Bcreated_at%282i%29%5D=1&post%5Bcreated_at%283i%29%5D=23&post%5Bcreated_at%284i%29%5D=18&post%5Bcreated_at%285i%29%5D=47&post%5Bbody%5D=It+works%21&commit=Create+Post
অনেকটা প্রতিক্রিয়ার মতো, একটি HTTP অনুরোধের মধ্যে থাকে:
- একটি অনুরোধ-লাইন (
POST /posts HTTP/1.1\r\n
), একটি পদ্ধতি টোকেন সমন্বিত (POST
), একটি অনুরোধ URI (/posts/
), এবং HTTP সংস্করণ (HTTP/1.1
), এর পরে একটি CRLF (একটি ক্যারেজ রিটার্ন:\r, তারপর লাইন ফিড:\n) লাইনের শেষ নির্দেশ করতে - হেডার লাইন (
Host: localhost:5678\r\n
) হেডার কী, তার পরে একটি কোলন, তারপর মান এবং একটি CRLF৷
৷ - একটি নতুন লাইন (বা একটি ডবল CRLF) অনুরোধ লাইন এবং শিরোনামগুলিকে বডি থেকে আলাদা করার জন্য:(
\r\n\r\n
) - ইউআরএল এনকোডেড POST বডি
session.gets
ব্যবহার করার পর অনুরোধের প্রথম লাইন (অনুরোধ-লাইন) নিতে, আমাদের কিছু হেডার লাইন এবং একটি বডি বাকি আছে। হেডার লাইনগুলি পেতে, আমাদের সেশন থেকে লাইনগুলি পুনরুদ্ধার করতে হবে যতক্ষণ না আমরা একটি নতুন লাইন খুঁজে পাই (\r\n
)।
প্রতিটি হেডার লাইনের জন্য, আমরা প্রথম কোলনে বিভক্ত করব। কোলনের আগে সবকিছুই হল চাবিকাঠি এবং পরে সবকিছুই হল মান। আমরা #strip
শেষ থেকে নতুন লাইন সরানোর মান।
বডি পেতে রিকোয়েস্ট থেকে আমাদের কত বাইট পড়তে হবে তা জানতে, আমরা "কন্টেন্ট-লেংথ" হেডার ব্যবহার করি, যা ব্রাউজার স্বয়ংক্রিয়ভাবে একটি অনুরোধ পাঠানোর সময় অন্তর্ভুক্ত করে।
# http_server.rb
# ...
headers = {}
while (line = session.gets) != "\r\n"
key, value = line.split(':', 2)
headers[key] = value.strip
end
body = session.read(headers["Content-Length"].to_i)
# ...
এখন, একটি খালি বস্তু পাঠানোর পরিবর্তে, আমরা একটি StringIO
পাঠাব আমরা অনুরোধের মাধ্যমে প্রাপ্ত শরীরের সাথে উদাহরণ. এছাড়াও, যেহেতু আমরা এখন অনুরোধের শিরোনাম থেকে কুকিজ পার্স করছি, আমরা সেগুলিকে HTTP_COOKIE
-এ র্যাক পরিবেশে যোগ করতে পারি। অনুরোধের সত্যতা পরীক্ষা পাস করার জন্য পরিবর্তনশীল।
# http_server.rb
# ...
status, headers, body = app.call({
# ...
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_COOKIE' => headers['Cookie'],
'rack.version' => [1,3],
'rack.input' => StringIO.new(body),
'rack.errors' => $stderr,
# ...
})
# ...
আমরা শুরু করছি. যদি আমরা সার্ভার পুনরায় চালু করি এবং আবার ফর্ম জমা দেওয়ার চেষ্টা করি, আপনি দেখতে পাবেন যে আমরা সফলভাবে আমাদের ব্লগে প্রথম পোস্ট তৈরি করেছি!
আমরা এই সময় গুরুত্ব সহকারে আমাদের ওয়েব সার্ভার আপগ্রেড করেছি। শুধুমাত্র একটি Rack অ্যাপ থেকে GET অনুরোধ গ্রহণ করার পরিবর্তে, আমরা এখন একটি সম্পূর্ণ Rails অ্যাপ পরিবেশন করছি যা POST অনুরোধগুলি পরিচালনা করে। এবং আমরা এখনও মোট পঞ্চাশ লাইনের বেশি কোড লিখিনি!
আপনি যদি আমাদের নতুন এবং উন্নত সার্ভারের সাথে খেলতে চান তবে কোডটি এখানে রয়েছে৷ আপনি যদি আরও জানতে চান বা কোনো নির্দিষ্ট প্রশ্ন করতে চান তাহলে @AppSignal-এ আমাদের জানান।