RFC 10008 এবং HTTP QUERY Method: FastAPI দিয়ে Framework-Agnostic Production-Ready Search API বানানোর সম্পূর্ণ Engineering Handbook

On this page
ভূমিকা: ১৬ বছর ধরে খুলে রাখা একটা অস্বস্তিকর ফাঁক!
প্রথমেই আমার সালাম নিবেন ভাই ও বোনেরা! আজকে একটা দারুণ ইন্টারেস্টিং বিষয় নিয়ে একটু বকবক করবো। আপনি কি রেডি?
বিশ্বাস করেন, ব্যাকএন্ড নিয়ে কাজ করতে গিয়ে আমি নিজে কতবার যে এই প্যারাটা খেয়েছি, তার কোনো হিসাব নাই! প্রতিটা ব্যাকএন্ড ইঞ্জিনিয়ারের লাইফে এমন একটা দিন আসে যখন তাকে একটা জটিল, হিজিবিজি search বা filter API বানাতে হয়। আর তখনই আমরা গিয়ে ধাক্কা খাই HTTP-এর একটা পুরনো, অস্বস্তিকর ফাঁকের সাথে।
আমরা সবাই জানি, GET একদম নিয়ম মেনে চলে, লক্ষ্মী ছেলের মতো safe আর idempotent, cache-ও করা যায় সহজে। কিন্তু ঝামেলা হলো, দুনিয়ার যাবতীয় ডেটা ওই এক চিলতে URL-এর ভেতরেই গুঁজে দিতে হয়! অন্যদিকে POST হলো একটু বাউন্ডুলে টাইপের, নিয়ম ভাঙে, request body-তে যা খুশি যত খুশি ডেটা পাঠানো যায়। কিন্তু semantics-এর নিয়ম অনুযায়ী এটা তো state পরিবর্তন করার জন্য, কোনো read-only অপারেশনের জন্য তো এটা বানানো হয়নি, তাই না?
আজকের এই আর্টিকেলটা শুধু শুকনো থিওরি লেকচার দেওয়ার জন্য না। এটা একটা সম্পূর্ণ engineering handbook! এখানে আমরা RFC 10008 এর প্রতিটা খুঁটিনাটি ডিটেইল, framework-agnostic implementation principle, Python FastAPI দিয়ে একদম রিয়েল-লাইফ reference implementation, ইনফ্রাস্ট্রাকচারের আসল সীমাবদ্ধতা, security consideration, আর আজকের দিনে পুরো ecosystem-এর কী অবস্থা, সব কিছু একদম পোস্টমর্টেম করে ফেলবো। আপনি যে ল্যাঙ্গুয়েজ বা ফ্রেমওয়ার্কেই কোড লিখুন না কেন, এই লেখাটা পড়ার পর নিজের প্রজেক্টে এটা খুব সহজেই implement করতে পারবেন।
GET এবং POST ঠিক কোথায় গিয়ে ধোঁকা দেয়?
কেন আমাদের নতুন কিছুর দরকার হলো? আসুন একটু হিসেব করি! RFC 10008 নিজেই চারটা সলিড কারণ দেখিয়ে দিয়েছে কেন GET জটিল query-র জন্য একদমই যথেষ্ট না:
১. URL-এর অনিশ্চিত লিমিট: একটা request যখন আপনার সার্ভার পর্যন্ত আসে, তখন সেটা পথে একাধিক proxy, load balancer আর CDN-এর ওপর দিয়ে ধাক্কা খেতে খেতে আসে। আর এদের প্রত্যেকের নিজস্ব একটা length limit থাকে! একটু বড় filter পাঠালেই দেখবেন কে যেন মাঝপথ থেকেই request-টা লাথি মেরে ফেলে দিয়েছে!
২. গোপনীয়তা ফাঁস: URL সচরাচর access log, browser history আর referrer header-এ একদম প্রকাশ্য দিবালোকে থেকে যায়। এখন চিন্তা করেন, আপনার filter-এ যদি কোনো sensitive ডেটা থাকে, সেটা কিন্তু অনিচ্ছাকৃতভাবেই সবার কাছে লিক হয়ে যাচ্ছে! বুঝলেন তো?
৩. হিজিবিজি অয়েসথাই কোড: nested filter object (যেমন price range, multiple category, sort array) যদি URL query string-এ পাঠাতে যান, সেটা দেখতে যে কী জঘন্য হয়, তা আমরা সবাই জানি! এটা readable রাখা প্রায় অসম্ভব একটা কাজ।
৪. ইনফ্রাস্ট্রাকচারের কনফিউশন: বাধ্য হয়ে যখন আপনি POST ব্যবহার করবেন, তখন আপনার load balancer বা CDN তো আর জানে না যে এই অপারেশনটা read নাকি write! ফলে caching, retry আর rate-limiting-এর মতো গুরুত্বপূর্ণ সিদ্ধান্তগুলো পুরোই ভুল হয়ে যায়।
একটু পেছনের গল্প: ১১ বছরের ড্রাফট থেকে অবশেষে RFC!
তখন ২০১০ সাল, HTTP-তে PATCH method আসার পর দীর্ঘ ১৬ বছর কেটে গেছে। ভাবা যায়? ১৬ বছর পর এই প্রথম HTTP-তে কোনো সম্পূর্ণ নতুন general-purpose method যোগ হলো!
এই QUERY-এর ড্রাফট (draft-ietf-httpbis-safe-method-w-body) HTTP Working Group-এ বছরের পর বছর ধরে iterate হয়েছে। অবশেষে ২০২৫ সালের ২০ নভেম্বর IESG-তে এটা formally approve হয়, এবং শেষমেশ ২০২৬ সালের ১৫ জুন RFC Editor এটাকে RFC 10008 হিসেবে অফিশিয়ালি পাবলিশ করে। এটা একটা ২৪ পাতার Proposed Standard, যেটা লিখেছেন Julian Reschke (greenbytes), James M. Snell (Cloudflare) এবং Mike Bishop (Akamai)। ১৬ বছরের অপেক্ষা বলে কথা!
RFC 10008 গভীরে গিয়ে দেখা যাক
ঠিক আছে এখন আসল কথায় আসি! স্পেসিফিকেশন আসলে আমাদের কী বলছে?
সংজ্ঞা এবং IANA রেজিস্ট্রেশন
RFC অনুযায়ী, QUERY request একটা resource-কে বলে দেয় যে request-এর সাথে সংযুক্ত content-টা একটা safe এবং idempotent উপায়ে প্রসেস করতে হবে, আর তার ফলাফলটা response হিসেবে ফেরত দিতে হবে। IANA-এর HTTP Method Registry-তে এটা ঠিক এভাবে রেজিস্টার করা আছে:
| Method Name | Safe | Idempotent | Specification |
|---|---|---|---|
| QUERY | Yes | Yes | Section 2, RFC 10008 |
Safe আর Idempotent জিনিসটা কী? এটা অনেকটা ঘরের সবচেয়ে শান্ত ছেলের মতো। আপনি তাকে যতবারই একই কাজ করতে বলেন না কেন, সে ঘরের কোনো আসবাবপত্র ভাঙচুর করবে না (মানে সার্ভারের কোনো state change করবে না)। ইনফ্রাস্ট্রাকচার এখন নিশ্চিতভাবে জানে এই request-টা কোনো ক্ষতি করবে না, তাই মাঝপথে কানেকশন ড্রপ খেলেও টেনশন নাই, এটাকে একদম নিরাপদে আবার পাঠানো যাবে, ঠিক GET-এর মতোই!
Body মানেই JSON না, Content-Type-ই আসল কিং!
এখানেই কিন্তু বেশিরভাগ মানুষ ভুল করে বসে! সবাই ভাবে request body থাকা মানেই সেটা JSON হতে হবে। আরে না ভাই! ফ্রেমওয়ার্ক-অ্যাগনস্টিক হওয়ার জন্য এই জায়গাটা বোঝা সবচেয়ে জরুরি।
QUERY request-এর body ঠিক কী ফরম্যাটে থাকবে, সেটা RFC নির্দিষ্ট করে দেয়নি। body এবং Content-Type header একসাথে মিলে সেই query-টাকে define করে। মানে আপনি চাইলে JSON, application/x-www-form-urlencoded, GraphQL query syntax, এমনকি আপনার নিজের বানানো কোনো কাস্টম SQL-জাতীয় DSL-ও পাঠাতে পারেন! শর্ত একটাই, সার্ভার এবং ক্লায়েন্ট দুইপক্ষকেই সেই Content-Type-টা বুঝতে হবে। RFC-এর নিজের দেওয়া একটা চমৎকার example দেখুন:
QUERY /contacts HTTP/1.1Host: example.orgContent-Type: application/x-www-form-urlencodedAccept: application/json
select=surname,givenname,email&limit=10&match=email=*@example.*Content-Type Validation একদম বাধ্যতামূলক!
RFC অনুযায়ী সার্ভারকে অবশ্যই missing বা inconsistent Content-Type header প্রত্যাখ্যান করতে হবে। আর ভুল হলে যথাযথভাবে 400 Bad Request, 415 Unsupported Media Type বা 422 Unprocessable Entity রিটার্ন করতে হবে। এটা কিন্তু কোনো optional best practice না ভাই, এটা স্পেসিফিকেশনের একটা MUST requirement!
Accept-Query header দিয়ে discoverability
এখন প্রশ্ন হলো, কোনো একটা resource যে QUERY সাপোর্ট করে কিনা, আর কোন Content-Type সে গ্রহণ করে, সেটা ক্লায়েন্ট বুঝবে কীভাবে? এই discoverability-র জন্য RFC একটা নতুন header field সংজ্ঞায়িত করেছে, যার নাম Accept-Query। এটা IANA-এর HTTP Field Name Registry-তেও যোগ হয়েছে। সাধারণত OPTIONS request-এর response-এ এটা পাঠানো হয়:
OPTIONS /products HTTP/1.1Host: api.example.com
HTTP/1.1 204 No ContentAllow: GET, QUERY, OPTIONSAccept-Query: application/jsonCORS-এর ব্যাপারে RFC নিজেই যা বলে
CORS-এর যে কী প্যারা, সেটা তো ফ্রন্টএন্ড-ব্যাকএন্ড জোড়া লাগাতে গিয়ে আমরা সবাই জানি, তাই না? RFC নিজেই স্পষ্ট করে বলে দিয়েছে যে QUERY কিন্তু CORS-safelisted method-এর তালিকায় নেই (সেই ভিআইপি তালিকায় শুধু GET, HEAD, আর POST আছে)। তাই ব্রাউজার থেকে কোনো cross-origin QUERY request পাঠালে সেটা বাধ্যতামূলকভাবে একটা preflight (OPTIONS) request ট্রিগার করবেই। এটা মাথায় রাখতেই হবে!
Framework-Agnostic Implementation Principle: যেকোনো স্ট্যাকে যা করতেই হবে
সত্যি কথা বলতে, আপনি Python, Node.js, Go, Java, বা Ruby যেটাই ব্যবহার করেন না কেন, নিচের ছয়টা গোল্ডেন রুল সবার জন্যই সমানভাবে প্রযোজ্য। একটু চট করে দেখে নেওয়া যাক:
1️ HTTP server layer-এ raw QUERY method গ্রহণ করা: আপনার application-এর একদম নিচে যে HTTP parser আছে (যেমন Uvicorn/Hypercorn, Node.js-এর HTTP module, বা Tomcat/Jetty), সেটাকে আগে QUERY-কে একটা বৈধ method হিসেবে চিনতে হবে। ফ্রেমওয়ার্কে সাপোর্ট থাকলেও নিচের transport layer যদি না চেনে, সে সাইলেন্টলি request ব্লক করে দেবে!
2️ Content-Type অনুযায়ী body parse করা: ঠিক POST-এর মতোই body parsing logic reuse করুন, কিন্তু Content-Type validate করে mismatch এড়াতে ভুলবেন না।
3️ Handler-কে সত্যিকারের read-only রাখা: এর ভেতরে কোনো database write বা side-effect ভুলেও রাখবেন না! কারণ ইনফ্রাস্ট্রাকচার এটাকে safe ধরে নিয়ে automatic retry মারতে পারে, আর retry হলে আপনার ডাটাবেজে duplicate এন্ট্রি পড়ে যাবে।
4️ Accept-Query এবং Allow header দিয়ে discoverability দেওয়া: OPTIONS response-এ একদম স্পষ্টভাবে জানিয়ে দিন যে আপনার resource QUERY সাপোর্ট করে।
5️ CORS middleware-এ explicit ভাবে QUERY যোগ করা: ডিফল্ট CORS কনফিগারেশন কখনোই এটা নিজে থেকে ধরে নেবে না।
6️ Method-aware টুলিং দিয়ে টেস্ট করা: curl -X QUERY, httpx বা k6 নেটিভভাবে কাস্টম method সাপোর্ট করে, কিন্তু JMeter-এর মতো কিছু টুলে একটু আলাদা configuration লাগে।
Reference Implementation: FastAPI (Python) দিয়ে
এই তো গেলো থিওরি কথা, এখন আসি আসল কোড নিয়ে! উদাহরণ হিসেবে আমি FastAPI বেছে নিয়েছি কারণ Python-এ এটা এখন সবচেয়ে জনপ্রিয় async framework। তবে উপরের ছয়টা প্রিন্সিপাল মাথায় রাখলে আপনি এটা Express, Spring, Django, বা Go দিয়েও একদম সহজে বানিয়ে ফেলতে পারবেন।
# main.pyfrom fastapi import FastAPI, Request, HTTPExceptionfrom fastapi.middleware.cors import CORSMiddlewarefrom pydantic import BaseModel, Fieldfrom typing import Optional, Literal
app = FastAPI(title="Product Search API")
# Principle 5: CORS middleware এ explicit ভাবে QUERY যোগ করাapp.add_middleware( CORSMiddleware, allow_origins=["[https://yourfrontend.com](https://yourfrontend.com)"], allow_methods=["QUERY", "OPTIONS"], allow_headers=["Content-Type", "Accept"],)
class PriceRange(BaseModel): min: Optional[float] = Field(default=None, ge=0) max: Optional[float] = Field(default=None, ge=0)
class ProductQuery(BaseModel): category: Optional[str] = None brand: Optional[str] = None price: Optional[PriceRange] = None sort: Optional[Literal["price", "-price", "name", "-name"]] = None page: int = Field(default=1, ge=1) limit: int = Field(default=20, le=100)
@app.api_route("/products", methods=["QUERY"])async def query_products(request: Request, payload: ProductQuery): # Principle 2: Content-Type validate করা (একদম মিস করা যাবে না!) content_type = request.headers.get("content-type", "") if "application/json" not in content_type: raise HTTPException( status_code=415, detail="This endpoint only accepts application/json for QUERY requests", )
# Principle 3: এই ফাংশনের ভেতরে কোনো write operation থাকবে না filtered = apply_filters(payload)
return { "query": payload.model_dump(exclude_none=True), "count": len(filtered), "results": filtered, }
@app.options("/products")async def products_options(): # Principle 4: Accept-Query দিয়ে discoverability from fastapi.responses import Response
return Response( status_code=204, headers={ "Allow": "GET, QUERY, OPTIONS", "Accept-Query": "application/json", }, )
def apply_filters(query: ProductQuery) -> list[dict]: # এখানে আপনার আসল database query logic বসবে return []খেয়াল করেছেন? এখানে আমি @app.api_route ব্যবহার করেছি। কারণ FastAPI-এর built-in decorator (যেমন @app.get বা @app.post) এর মধ্যে এখনো QUERY নেই, কিন্তু api_route-এর methods প্যারামিটারে যেকোনো string পাস করা যায়। Starlette শুধু incoming request-এর scope["method"] কে আপনার দেওয়া list-এর সাথে ম্যাচ করে নেয়, তাই এটা একদম মাখনের মতো কাজ করে!
OpenAPI ডকুমেন্টেশনের একটা ছোট্ট সীমাবদ্ধতা
OpenAPI স্পেসিফিকেশনের method list এখনো QUERY অন্তর্ভুক্ত করেনি। তাই Swagger UI বা ReDoc এই endpoint-টা সঠিকভাবে না-ও দেখাতে পারে, এটা দেখে ঘাবড়াবেন না কিন্তু! runtime-এ আপনার endpoint একদম পুরোপুরি কাজ করবে। GUI ডকুমেন্টেশনের বদলে সরাসরি HTTP client দিয়ে টেস্ট করে দেখবেন।
Pytest দিয়ে টেস্ট লেখা
কোড লিখলাম আর টেস্ট করব না, তা কি হয়? আসুন একটা ছোট async টেস্ট লিখে ফেলি:
# test_products.pyimport pytestfrom httpx import AsyncClient, ASGITransportfrom main import app
@pytest.mark.asyncioasync def test_query_products_success(): transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://test") as client: response = await client.request( method="QUERY", url="/products", json={"category": "phone", "brand": "apple"}, ) assert response.status_code == 200 assert response.json()["query"]["category"] == "phone"আর টার্মিনালে curl দিয়ে ম্যানুয়াল ভেরিফিকেশন করতে চাইলে:
curl -i -X QUERY http://localhost:8000/products \ -H "Content-Type: application/json" \ -d '{"category": "phone", "brand": "apple", "price": {"min": 100, "max": 1000}}'Server-Layer Trap: Uvicorn Parser Internals (এখানেই আসল রহস্য!)
দাঁড়ান দাঁড়ান যাবেন না! এখানে একটা মারাত্মক প্রোডাকশন ট্র্যাপ আছে! আমাদের ১ নাম্বার প্রিন্সিপালটা এখানে সবচেয়ে বেশি গুরুত্বপূর্ণ হয়ে দাঁড়ায়। কারণ উপরের কোড আপনার লোকাল মেশিনে পারফেক্টলি কাজ করলেও প্রোডাকশনে গিয়ে সাইলেন্টলি ভেঙে পড়তে পারে! কেন জানেন?
Uvicorn মূলত দুই ধরনের HTTP parser দিয়ে চলতে পারে। আপনি যখন pip install uvicorn[standard] করেন, তখন ডিফল্টভাবে যেটা ইনস্টল হয় সেটা হলো httptools। এটা মূলত Node.js-এর llhttp parser-এর একটা Python wrapper। এই parser-টা হলো একদম কড়া হেডমাস্টারের মতো! সে method name গুলো একটা fixed, case-sensitive টেবিলের সাথে ম্যাচ করে। তার মানে, তার কাছে "QUERY" আর "query" সম্পূর্ণ ভিন্ন দুইটা জিনিস! ভুল করে লোয়ারকেস "query" এলে httptools সেটা চিনতেই পারে না, আর আপনার FastAPI কোড পর্যন্ত পৌঁছানোর আগেই গেট থেকে সরাসরি 400 Bad Request দিয়ে লাথি মেরে বের করে দেয়!
বিপরীতে Uvicorn-এর দ্বিতীয় অপশন হলো h11। এটা একটা pure-Python HTTP/1.1 implementation, এবং এটা অনেক বেশি চিল বন্ধু টাইপের! সে লোয়ারকেস method নিয়েও কোনো অভিযোগ করে না, বরং সেটাকে একদম routing পর্যন্ত পৌঁছে দেয়। সেখানে matched route না পেলে সে ভদ্রভাবে 405 Method Not Allowed রিটার্ন করে।
তাহলে সমাধান কী? টার্মিনালে parser এক্সপ্লিসিটভাবে সিলেক্ট করে দিবেন:
# Parser এক্সপ্লিসিটভাবে সিলেক্ট করাuvicorn main:app --http httptools # ডিফল্ট, দ্রুততর, কিন্তু কেস-সেনসিটিভuvicorn main:app --http h11 # ধীরগতি, কিন্তু বেশি lenientএখন প্রশ্ন হলো, এই লোয়ারকেস method আসে কোথা থেকে? এর উত্তর লুকিয়ে আছে আমাদের ক্লায়েন্ট-সাইডে!
Client-Side Gotcha: fetch() কেন আপনার QUERY কে lowercase রেখে দেয়?
ব্রাউজারের fetch() নিয়ে একটা আজব কাহিনী শুনবেন? WHATWG Fetch স্পেসিফিকেশনে "normalize a method" নামে একটা স্টেপ আছে। এই স্টেপের কাজ হলো মেথডের নামকে বড় হাতের অক্ষর বা uppercase করে দেওয়া। কিন্তু মজা হলো, সে শুধু ছয়টা classic method-কেই uppercase করে: DELETE, GET, HEAD, OPTIONS, POST আর PUT!
আমাদের বেচারা QUERY এই ভিআইপি তালিকায় নেই, কারণ এটা তৈরি হয়েছে এই ছয়টা চূড়ান্ত হওয়ার অনেক অনেক পরে! ফলে কোনো ফ্রন্টএন্ড ডেভেলপার ভুল করে যদি fetch(url, { method: "query" }) লিখে ফেলে, ব্রাউজার সেটাকে ঠিক সেভাবেই, লোয়ারকেস অবস্থাতেই wire-এ পাঠিয়ে দেয়! এই আচরণ নিয়ে ২০২৬ সালের জুনে whatwg/fetch রিপোজিটরিতে একটা আলাদা ইস্যু ওপেন করা হয়েছে, যেখানে QUERY কে normalize করা উচিত কিনা তা নিয়ে এখনো জোর আলোচনা চলছে।
তাই ফ্রন্টএন্ড থেকে লেখার সময় একদম সঠিকভাবে লিখবেন:
const response = await fetch("/products", { method: "QUERY", // অবশ্যই uppercase রাখতে হবে, ভুলবেন না! headers: { "Content-Type": "application/json" }, body: JSON.stringify({ category: "phone", brand: "apple" }),});const data = await response.json();আর আপনি যদি Node.js ব্যাকএন্ড থেকে অন্য সার্ভিসে request পাঠাতে চান, তাহলে জেনে খুশি হবেন যে Node-এর নিজস্ব undici লাইব্রেরি (যেটা Node-এর built-in fetch-এর পেছনে কাজ করে) ২০২৬ সালের গ্রীষ্মে একটা merged pull request-এর মাধ্যমে QUERY সাপোর্ট যোগ করেছে! সেই সাথে একটা body-aware cache key implementation reference-ও দিয়ে দিয়েছে। আর Node.js তার নিজস্ব HTTP parser দিয়ে ২০২৪ সালের শুরু থেকেই (Node 21.7.2 এবং 22+ ভার্সনে) QUERY কে একটা বৈধ method হিসেবে চিনতে পারে।
Python-এর httpx দিয়ে করতে চাইলে:
import httpx
async with httpx.AsyncClient() as client: response = await client.request( method="QUERY", url="http://localhost:8000/products", json={"category": "phone", "brand": "apple"}, )Caching: যেটার জন্য এতো নাটক, সেটার আজকের বাস্তবতা কী?
QUERY-এর সবচেয়ে বড় selling point-ই তো ছিল caching, তাই না? RFC-এর নিয়ম অনুযায়ী এর response cache করা সম্ভব। কিন্তু এখানে একটা টুইস্ট আছে! cache key শুধু URL দিয়ে বানালে চলবে না, request body-ও cache key-এর একটা অবিচ্ছেদ্য অংশ হতে হবে! RFC নিজেই একটা মারাত্মক ঝুঁকির কথা বলেছে (যেটা আমি ব্র্যাকেটে বলে রাখছি, কোনো cache implementation যদি body ভুলভাবে normalize করে এবং সেটার কারণে false positive তৈরি হয়, তাহলে এক ইউজারের সেনসিটিভ ডেটা আরেক ইউজারের কাছে চলে যেতে পারে!)।
বাস্তবতা হলো, ২০২৬ সালের জুলাই পর্যন্ত Chrome বা Firefox কেউই একই QUERY request দ্বিতীয়বার পাঠালে সেটা browser cache থেকে সার্ভ করে না! browser-level caching এখনো standardization আলোচনার পর্যায়েই আছে, Fetch স্পেসিফিকেশনে এটা কীভাবে হবে তা নিয়ে এখনো চূড়ান্ত সিদ্ধান্ত হয়নি। That's the real game!
নিজস্ব Caching Layer বানালে সাবধান!
আপনি যদি নিজে একটা caching layer বানান (যেমন Redis, CDN edge rule বা reverse proxy দিয়ে), সেই cache key তৈরিতে request body ঠিকমতো hash বা normalize না করলে দুইটা ভিন্ন filter criteria একই cache entry শেয়ার করে ফেলতে পারে! এর ফলাফল হতে পারে cache poisoning বা cache deception! body-এর একটা cryptographic digest (যেমন SHA-256, RFC 9530-এর Content-Digest header ব্যবহার করে) cache key-তে যোগ করাই সবচেয়ে নিরাপদ পদ্ধতি। এই ধরনের digest-aware cache নিয়ে আলোচনা এখনো IETF HTTP Working Group-এ চলছে।
Infrastructure ও Ecosystem Adoption: আজকের বাস্তব অবস্থা
এই টেবিলটাই সম্ভবত আজকের আর্টিকেলের সবচেয়ে বেশি প্র্যাকটিক্যাল ভ্যালু বহন করে। কারণ এটা স্পষ্ট বলে দেয় ঠিক কোথায় আজকে আপনি রিস্ক নিচ্ছেন আর কোথায় একদম সেফ:
| Component | Status (জুলাই ২০২৬) |
|---|---|
| Node.js core HTTP parser | নেটিভ সাপোর্ট, Node 21.7.2 এবং 22+ থেকে |
| Node.js undici / fetch | সাপোর্ট merged, body-aware cache key সহ |
| nginx | Basic RFC 10008 সাপোর্ট মার্জ হয়েছে |
| Chrome, Firefox (browser fetch) | Scripted fetch কাজ করে, কিন্তু caching এখনো implement হয়নি |
| Kong Gateway | Method uppercase হতে হয়, native QUERY awareness এখনো emerging |
| AWS API Gateway | এখনো নেটিভভাবে সাপোর্ট করে না, শুধু GET/POST/PUT/PATCH/DELETE/HEAD/ANY, ANY দিয়ে workaround করলে body strip বা 403/405 হওয়ার ঝুঁকি আছে |
| Spring Framework | সাপোর্ট যোগ করার issue/PR এখনো ওপেন |
| Ruby on Rails | Community proposal আলোচনায়, এখনো merged না |
| Eclipse Jetty, Apache Tomcat | সাপোর্টের জন্য ইস্যু ওপেন, active development চলছে |
| OpenAPI tooling | OpenAPI 3.2 এ documented, কিন্তু Swagger UI/ReDoc rendering এখনো অসম্পূর্ণ |
Production Readiness মাথায় রাখুন!
এই টেবিল দেখেই বোঝা যায় কেন public-facing, browser-নির্ভর traffic-এ আজকেই একলাফে পুরোপুরি QUERY-তে মাইগ্রেট করা ঠিক হবে না। Server-to-server communication-এ, বিশেষ করে Node.js এবং সঠিকভাবে কনফিগার করা Python backend-এর মধ্যে, এটা আজই ১০০% নির্ভরযোগ্য। কিন্তু আপনার পুরো path (client, CDN, API Gateway, WAF, backend) সবগুলো layer QUERY সাপোর্ট নিশ্চিত না করা পর্যন্ত পাবলিক API-তে POST /search কে fallback হিসেবে রেখে দিন।
Security Consideration
QUERY-কে "safe" method হিসেবে চিহ্নিত করার মানে হলো, অনেক ইনফ্রাস্ট্রাকচার কম্পোনেন্ট (যেমন WAF, rate limiter, বা CSRF protection) এটাকে স্বয়ংক্রিয়ভাবে GET-এর মতোই "নিরাপদ ও non-mutating" ধরে নেবে। এখন কোনো ভুল implementation যদি QUERY handler-এর ভেতরে সত্যিকারের state mutation রেখে দেয়, তাহলে সেটা একটা মারাত্মক সিকিউরিটি হোল তৈরি করবে, কারণ ইনফ্রাস্ট্রাকচার এটাকে CSRF-exempt ধরে নেবে! এছাড়া অপরিচিত method নিয়ে পুরনো WAF আর reverse proxy-র আচরণও বেশ অসংগত হয়। কিছু সিস্টেম অজানা method দেখলে পুরো request ব্লক করে দেয়, আবার কিছু সিস্টেম না চিনেই pass through করে দেয়। সাবধানে হ্যান্ডেল করবেন!
Migration Strategy: ধাপে ধাপে কীভাবে এগোবেন?
একলাফে সব মাইগ্রেট করার কোনো দরকার নাই ভাই! একটা বাস্তবসম্মত এবং স্মার্ট পদ্ধতি হলো dual-support। আপনার ব্যাকএন্ড একই business logic-কে দুইটা route-এ এক্সপোজ করবে:
@app.api_route("/products", methods=["QUERY"])async def query_products(request: Request, payload: ProductQuery): return await run_search(payload)
@app.post("/products/search")async def post_search_fallback(payload: ProductQuery): return await run_search(payload)ক্লায়েন্ট-সাইডে প্রথমে OPTIONS /products করে Allow header চেক করে নেবেন। যদি দেখেন QUERY আছে, তাহলে সেটা বুক ফুলিয়ে ব্যবহার করুন, আর না থাকলে শান্তশিষ্টভাবে POST /products/search-এ fallback করুন। এই ধরনের feature detection আপনাকে ecosystem-এর পরিপক্কতার সাথে সাথে স্বয়ংক্রিয়ভাবে মাইগ্রেট হতে সাহায্য করবে, কোনো hard cutover ছাড়াই!
QUERY বনাম GraphQL: একটা সাধারণ ভুল বোঝাবুঝি
অনেকেই ভাবেন QUERY method এসে বুঝি GraphQL-কে খেয়ে ফেলবে বা replace করবে! আরে না ভাই, এই দুইটা সম্পূর্ণ ভিন্ন লেয়ারে কাজ করে।
GraphQL হলো একটা query language এবং schema system, যেটা সাধারণত একটা single POST endpoint-এর ওপর বসানো থাকে। অন্যদিকে QUERY method নিজে কোনো query language না, এটা শুধু transport-লেয়ারের একটা HTTP method, যেটা GraphQL-সহ যেকোনো query language-কে সঠিক semantics দিয়ে বহন করতে পারে। ভবিষ্যতে GraphQL সার্ভারগুলো চাইলে POST এর বদলে QUERY method ব্যবহার করে caching এবং idempotency-র জোস সব সুবিধা নিতে পারে, কিন্তু তাতে GraphQL-এর নিজস্ব query syntax মোটেও বদলাবে না। বুঝতে পেরেছেন?
Testing ও Tooling Checklist
টেস্ট করার সময় নিচের টুলগুলোর দিকে একটু নজর রাখবেন:
- curl:
-X QUERYদিয়ে সরাসরি সাপোর্ট করে, সবচেয়ে নির্ভরযোগ্য quick-test টুল। - httpx (Python):
client.request(method="QUERY", ...)দিয়ে একদম মাখনের মতো কাজ করে। - k6: কাস্টম HTTP method নেটিভভাবে সাপোর্ট করে, load testing-এর জন্য জোস একটা অপশন।
- JMeter: ডিফল্টভাবে
QUERYলিস্টে থাকে না, HTTP sampler-এ আলাদা property configuration করে নিতে হয়। - Postman/Swagger UI: এখনো একটু inconsistent, OpenAPI tooling পুরোপুরি catch up করেনি, তাই এগুলোর ওপর চোখ বন্ধ করে নির্ভর না করাই ভালো।
Production Checklist (সংক্ষেপে এক নজরে)
- ASGI সার্ভারের HTTP parser এক্সপ্লিসিটভাবে ঠিক করুন, টেস্ট ও প্রোডাকশনে সবসময় একই parser রাখুন।
- ফ্রন্টএন্ড কোডে method name সবসময় uppercase
"QUERY"রাখুন এবংallow_methods-এ CORS preflight টেস্ট করে নিন। - Content-Type validation বাস্তবায়ন করুন, mismatch হলে গেট থেকেই 400/415/422 রিটার্ন করুন।
OPTIONS-এAccept-QueryএবংAllowheader দিয়ে discoverability নিশ্চিত করুন।- Browser caching-এর ওপর অন্ধের মতো নির্ভর করবেন না, নিজস্ব caching layer বানালে body digest cache key-তে যোগ করুন।
- WAF, API Gateway এবং CDN আপনার path-এ
QUERYকীভাবে হ্যান্ডেল করছে আলাদাভাবে verify করুন, বিশেষ করে AWS API Gateway-এর মতো সার্ভিসে। - পাবলিক-facing API-তে
POST /search-কে fallback হিসেবে রাখুন যতক্ষণ পুরো infrastructure path native সাপোর্ট নিশ্চিত না করছে।
সংক্ষেপে বোঝার জন্য একটা জোস Analogy!
পুরো বিষয়টা যদি এখনো একটু ঘোলাটে লাগে, তাহলে এই সহজ তুলনাটা মনে রাখেন:
GET হলো খোলা পোস্টকার্ড পাঠানোর মতো। লেখার জায়গা একদম সীমিত, রাস্তার পিয়ন থেকে শুরু করে সবাই সেটা পড়তে পারে, কিন্তু এটা সম্পূর্ণ safe।
POST হলো একটা শক্ত করে সিল করা পার্সেল পাঠানো, যার ভেতরে কিছু তৈরি বা পরিবর্তনের স্পষ্ট নির্দেশ থাকে।
আর আমাদের নতুন বন্ধু QUERY হলো একটা লাইব্রেরির আর্কাইভে ডিটেইলড রিকুইজিশন ফর্ম ভরে পাঠানো! আপনি লাইব্রেরির কোনো বই বদলাতে বা ছিঁড়তে বলছেন না, শুধু জটিল সব শর্ত উল্লেখ করে সঠিক তথ্যটা খুঁজে বের করতে বলছেন। পার্থক্য হলো, এই আর্কাইভ সিস্টেমটা সবেমাত্র নতুন চালু হয়েছে, তাই কিছু কাউন্টার এখনো পুরনো নিয়মেই ফর্ম প্রসেস করছে, আর কিছু কাউন্টার এখনো সেই ফর্মটাই চেনে না! উপমাটা কেমন লাগলো? একদম সহজ না?
FAQ
QUERY কি POST-কে সম্পূর্ণ replace করে ফেলবে?
না ভাই, একদমই না! এটা শুধু সেই নির্দিষ্ট gap পূরণ করে যেখানে safe read operation-এ request body দরকার হয়। state পরিবর্তনকারী operation-এর জন্য POST, PUT, PATCH, আর DELETE আগের মতোই রাজত্ব করবে।
আজকেই কি প্রোডাকশনে ব্যবহার করা যাবে?
Server-to-server communication-এ, বিশেষ করে যেখানে আপনি নিজেই client এবং server দুইটাই নিয়ন্ত্রণ করেন, সেখানে আজকেই ১০০% করা যাবে। তবে Public browser-facing API-তে upstream table-এ দেখানো সীমাবদ্ধতার কারণে এখনো একটু সাবধানে এগোনো উচিত।
শেষ কথা ও শুভকামনা!
আজকে তো বহুত বকবক করলাম! আশা করি পুরো বিষয়টা একদম পানির মতো পরিষ্কার হয়ে গেছে। ১৬ বছরের পুরনো একটা সমস্যার সমাধান চোখের সামনে দেখতে পাওয়ার আনন্দই আলাদা, তাই না?
আপনার পরবর্তী প্রজেক্টে এই QUERY method ট্রাই করে দেখবেন কিনা, আমাকে কিন্তু জানাতে ভুলবেন না! আর কোনো জায়গায় বুঝতে সমস্যা হলে বা কোনো প্রশ্ন থাকলে কমেন্টে চট করে লিখে ফেলুন। আমার জন্য অনেক অনেক দোয়া করবেন, আমিও আপনাদের সবার জন্য দোয়া করি। আপনাদের অনেক অনেক ধন্যবাদ এতোক্ষণ ধরে আমার এই হিজিবিজি হজবরল লেখাটা পড়ার জন্য!
References
Specification:
- RFC 10008, The HTTP QUERY Method (IETF): https://www.rfc-editor.org/info/rfc10008
- HTTP Semantics, RFC 9110: https://www.rfc-editor.org/rfc/rfc9110.html
- Content-Digest header field, RFC 9530: https://www.rfc-editor.org/rfc/rfc9530.html
- IANA HTTP Field Name Registry (Accept-Query): https://www.iana.org/assignments/http-fields
Implementation and Ecosystem Discussions:
- FastAPI Discussion #5520, APIRouter HTTP Method Query: https://github.com/fastapi/fastapi/discussions/5520
- Uvicorn httptools_impl.py Source Code: https://github.com/Kludex/uvicorn/blob/main/uvicorn/protocols/http/httptools_impl.py
- Node.js undici, QUERY method support (merged PR): https://github.com/nodejs/undici/pull/5459
- WHATWG Fetch, Issue #1938 (method normalization, CORS, caching): https://github.com/whatwg/fetch/issues/1938
- Mozilla Standards Positions, RFC 10008: https://github.com/mozilla/standards-positions/issues/1430
- Spring Framework, QUERY method support issue: https://github.com/spring-projects/spring-framework/issues/36988
- Ruby on Rails, QUERY method proposal discussion: https://discuss.rubyonrails.org/t/proposal-support-for-the-http-query-method-rfc-10008/91255
- Ecosystem Adoption Tracker, QUERY Method: https://github.com/jeswr/http-query-adoption
- AWS re:Post, HTTP QUERY Method and API Gateway: https://repost.aws/questions/QUMU66SxOLT1qXBVZaBHCtxA/http-query-method General Reference:
- MDN Web Docs, HTTP Request Methods: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods