एक सफल रिस्पॉन्स भेजें, उसका बॉडी क्रमशः लिखें और संदेश को सामान्य तरीके से पूरा करें। Mac Terminal का यह वॉकथ्रू दिखाता है कि हर हिस्से को अलग रिस्पॉन्स माने बिना या 206 Partial Content का उपयोग किए बिना एक पूरा सिंथेटिक दस्तावेज़ कैसे भेजें।

काम है सार्वजनिक होस्टिंग के लिए बनाया गया एक HTML पेज परोसना और फिर जाँचना कि क्लाइंट को उसकी पूरी सामग्री मिली है। यह प्रदर्शन स्थानीय रूप से चलता है और कुछ भी प्रकाशित नहीं करता। आपको Terminal, curl और आधिकारिक Node.js डाउनलोड पेज से समर्थित Node.js LTS रिलीज़ चाहिए।

हिस्सों में भेजने का मतलब आंशिक रिस्पॉन्स नहीं है

रिस्पॉन्स का अर्थ और उसे भेजने का तरीका अलग-अलग सवालों के जवाब देते हैं। सामान्य सफल GET के लिए 200 OK सफल पुनर्प्राप्ति दर्शाता है। 206 Partial Content का अर्थ है कि सर्वर किसी रेंज अनुरोध को पूरा करने के लिए किसी प्रतिनिधित्व के चुने हुए हिस्से भेज रहा है। डेटा को कई बार लिखने से रेंज अनुरोध नहीं बनता। RFC 9110: HTTP Semantics, 206 Partial Content।

उदाहरण के लिए, परिचय, विवरण और समापन पैराग्राफ वाली एक सिंथेटिक गाइड क्रमशः पहुँच सकती है और फिर भी एक ही दस्तावेज़ बनी रहती है। उसके रिस्पॉन्स हेडर एक बार भेजें, फिर बॉडी के वे हिस्से क्रम से भेजें। हर हिस्से के आगे नई HTTP स्टेटस लाइन न जोड़ें।

HTTP/1.1 का chunked transfer coding बॉडी बाइट्स को हेक्साडेसिमल लंबाइयों के साथ लपेटता है और शून्य आकार के chunk तथा उसके बाद आवश्यक खाली पंक्ति पर समाप्त होता है। ये फ्रेमिंग बाइट्स डेटा भेजने की सीमाएँ तय करते हैं; ये लेख की सामग्री नहीं हैं। भेजने वाले को Content-Length और Transfer-Encoding को एक साथ नहीं भेजना चाहिए। RFC 9112: Chunked Transfer Coding, Content-Length।

व्यावहारिक नियम यह है कि HTTP लाइब्रेरी को संदेश की फ्रेमिंग संभालने दें। आपका ऐप्लिकेशन दस्तावेज़ की सामग्री देता है और बताता है कि सामग्री कब पूरी हो गई है।

आपको कौन-सा स्ट्रीमिंग तरीका इस्तेमाल करना चाहिए?

समर्थित तरीका HTTP संस्करण पर निर्भर करता है। नीचे दिए गए तीनों संस्करणों में पूरी बॉडी को स्ट्रीम किया जा सकता है, लेकिन उनकी फ्रेमिंग अलग-अलग होती है।

HTTP संस्करण बॉडी की सामग्री कैसे जाती है पूरा होने का तरीका
chunked coding के साथ HTTP/1.1 लंबाई के साथ चिह्नित chunks अंतिम शून्य आकार का chunk और पूरी हुई trailer section
HTTP/2 DATA frames अंतिम frame पर END_STREAM, जिसमें किसी भी header block का पूरा होना शामिल है
HTTP/3 DATA frames रिस्पॉन्स पूरा होना और stream की भेजने वाली दिशा बंद होना

HTTP/2 में HTTP/1.1 का chunked transfer coding स्पष्ट रूप से प्रतिबंधित है। HTTP/3 transfer codings परिभाषित नहीं करता और Transfer-Encoding हेडर को प्रतिबंधित करता है। हर कनेक्शन पर Transfer-Encoding: chunked थोपने के बजाय सर्वर को अपने प्रोटोकॉल की फ्रेमिंग लागू करने दें। RFC 9113: HTTP Message Exchanges, RFC 9114: HTTP Message Framing।

जो छोटा दस्तावेज़ पहले से तैयार है, उसे सामान्य तरीके से भेजना आम तौर पर सबसे आसान उपाय है। सामग्री समय के साथ तैयार हो रही हो, तो क्रमशः लिखना उपयोगी होता है। नीचे का उदाहरण जानबूझकर तीन writes के बीच अंतराल रखता है, ताकि आप व्यवहार देख सकें; ये देरी सिर्फ़ उदाहरण के लिए हैं, प्रकाशन की सिफ़ारिश नहीं।

अपने Mac पर पूरा स्ट्रीमिंग दस्तावेज़ शुरू करें

Terminal खोलें और आवश्यक चीज़ें जाँचें:

node --version
curl --version

अगर node उपलब्ध नहीं है, तो Node.js डाउनलोड पेज से समर्थित LTS रिलीज़ इंस्टॉल करें, फिर Terminal की नई विंडो खोलें। यह प्रोग्राम केवल अंतर्निहित मॉड्यूल इस्तेमाल करता है और इसे npm पैकेजों की ज़रूरत नहीं है।

नीचे दिया पूरा ब्लॉक Terminal में पेस्ट करें। यह प्रदर्शन पते http://127.0.0.1:8080/document पर HTTP/1.1 सर्वर शुरू करता है। तीनों स्ट्रिंग मिलकर एक पूरा सिंथेटिक पेज बनाती हैं।

node <<'JS'
const http = require('node:http');

const parts = [
  '<!doctype html>\n<html lang="en"><head><meta charset="utf-8">' +
    '<title>Paper Shapes</title></head><body><main>\n' +
    '<h1>Paper Shapes</h1>\n',
  '<p>A circle, a square, and a triangle make this sample collection.</p>\n',
  '<p>End of the paper shapes guide.</p>\n</main></body></html>\n'
];

const server = http.createServer((req, res) => {
  res.on('error', error => console.error(error.message));
  if (req.method !== 'GET' || req.url !== '/document') {
    res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end('No sample document at this path.\n');
    return;
  }

  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  let index = 0;
  const timer = setInterval(() => {
    if (res.destroyed) {
      clearInterval(timer);
      return;
    }
    res.write(parts[index++]);
    if (index === parts.length) {
      clearInterval(timer);
      res.end();
    }
  }, 300);
  res.on('close', () => clearInterval(timer));
});

server.on('error', error => {
  console.error(error.message);
  process.exitCode = 1;
});
server.listen(8080, '127.0.0.1', () => {
  console.log('Sample: http://127.0.0.1:8080/document');
});
JS

ऐप्लिकेशन writeHead() को एक बार, write() को तीन बार और end() को एक बार कॉल करता है। Node के दस्तावेज़ बताते हैं कि लगातार की गई writes एक ही रिस्पॉन्स बॉडी के हिस्से हैं और हर रिस्पॉन्स पूरा करने के लिए end() ज़रूरी है। HTTP फ्रेमिंग Node संभालता है; स्ट्रिंग्स में chunk की लंबाइयाँ या अंतिम शून्य न जोड़ें। Node.js HTTP documentation।

यह तीन छोटी स्ट्रिंगों वाला सीमित प्रदर्शन है। बड़े या लगातार डेटा बनाने वाले स्रोत के लिए backpressure का ध्यान रखें: जब write() false लौटाए, तो drain आने तक डेटा बनाना रोकें और साथ ही कनेक्शन बंद होने तथा त्रुटियों को भी संभालें। Node.js response writing documentation।

यह Terminal विंडो चालू रहने दें। अगर यह EADDRINUSE दिखाए, तो पोर्ट 8080 पहले से इस्तेमाल हो रहा है; सर्वर में कोई दूसरा उपलब्ध पोर्ट चुनें और नीचे दिए गए हर क्लाइंट कमांड में भी पोर्ट बदलें। काम पूरा होने पर Control-C दबाकर प्रदर्शन रोकें।

डिकोड की गई सामग्री और HTTP chunk फ्रेमिंग, दोनों देखें

दूसरी Terminal विंडो में दस्तावेज़ का अनुरोध करें:

curl --http1.1 --include --no-buffer --max-time 5 \
  http://127.0.0.1:8080/document

एक 200 OK रिस्पॉन्स, text/html; charset=utf-8 का Content-Type और संयोजित पेज की सामग्री मिलने की अपेक्षा करें। इस HTTP/1.1 उदाहरण में रिस्पॉन्स को Transfer-Encoding: chunked इस्तेमाल करना चाहिए। Curl आम तौर पर ट्रांसफ़र फ्रेमिंग को डिकोड कर देता है, इसलिए उसका सामान्य आउटपुट हेक्साडेसिमल chunk आकारों के बजाय दस्तावेज़ दिखाता है।

फ्रेमिंग देखने के लिए --raw के साथ अनुरोध दोहराएँ:

curl --http1.1 --include --no-buffer --raw --max-time 5 \
  http://127.0.0.1:8080/document

Curl के दस्तावेज़ों के अनुसार --raw सामग्री और ट्रांसफ़र एन्कोडिंग की आंतरिक डिकोडिंग बंद करता है। --no-buffer curl का आउटपुट बफ़र करना बंद करता है, और --max-time 5 ऑपरेशन की समय-सीमा तय करता है। इन विकल्पों से यह अनिश्चित समय तक प्रतीक्षा करने वाला कमांड नहीं, बल्कि एक छोटा डायग्नोस्टिक बन जाता है। curl manual।

कच्चे आउटपुट में सामग्री के चारों ओर आकार वाली पंक्तियाँ होती हैं, फिर अंतिम शून्य आकार का chunk आता है। उस टर्मिनेटर के बिना HTTP/1.1 का chunked बॉडी अधूरा है। तय लंबाई वाली फ्रेमिंग में घोषित लंबाई से कम बाइटें मिलने पर संदेश अधूरा है। RFC 9112: Handling Incomplete Messages।

कच्चे आउटपुट का इस्तेमाल सिर्फ़ ट्रांसपोर्ट देखने के लिए करें। उसकी आकार वाली पंक्तियाँ पेज फ़ाइल में कॉपी न करें और उन्हें दस्तावेज़ के हिस्से न मानें।

प्रोसेस करने से पहले पूरा दस्तावेज़ प्राप्त करें

अगर अगले चरण में आपको पूरा पेज चाहिए, तो उसे पार्स करने, सहेजने या किसी और तरह स्वीकार करने से पहले पूरी बॉडी पढ़ें। WHATWG रिस्पॉन्स बॉडी को एक stream के रूप में परिभाषित करता है; उसका text() तरीका बॉडी को पूरी तरह पढ़कर एक स्ट्रिंग लौटाता है और बॉडी पढ़ने में विफलता होने पर त्रुटि देता है। सिर्फ़ रिस्पॉन्स हेडर मिल जाने से यह साबित नहीं होता कि बॉडी पूरी हो गई है। WHATWG Fetch Standard: Body mixin, Processing responses।

सर्वर चालू रहने पर यह पूरा क्लाइंट दूसरी Terminal विंडो में पेस्ट करें:

node --input-type=module <<'JS'
try {
  const response = await fetch('http://127.0.0.1:8080/document', {
    signal: AbortSignal.timeout(5000)
  });
  if (response.status !== 200) {
    throw new Error(`Expected 200; received ${response.status}`);
  }

  const html = await response.text();
  const marker = 'End of the paper shapes guide.';
  if (!html.includes(marker) || !html.endsWith('</html>\n')) {
    throw new Error('Expected sample ending is missing');
  }

  console.log(html);
  console.log('Complete sample document received.');
} catch (error) {
  console.error('Document not accepted:', error.message);
  process.exitCode = 1;
}
JS

Node में ब्राउज़र के साथ संगत fetch() कार्यान्वयन उपलब्ध है। इसका AbortSignal.timeout() तरीका ऐसा signal बनाता है जो बताए गए मिलीसेकंड बाद अनुरोध रद्द कर देता है; यहाँ पाँच सेकंड की सीमा इस छोटे स्थानीय उदाहरण के लिए दी गई है। Node.js global objects documentation, AbortSignal timeout documentation।

क्लाइंट दो अलग बातें जाँचता है: बॉडी को सफलतापूर्वक पढ़ना और अपेक्षित नमूना समापन मिलना। दूसरी जाँच ऐप्लिकेशन के लिए विशेष डायग्नोस्टिक है। HTTP किसी दस्तावेज़ को सफलतापूर्वक ट्रांसफ़र कर सकता है, भले ही आपका ऐप्लिकेशन end() कॉल करने से पहले गलती से उसे छोटा कर दे। समापन चिह्न उस खास गलती को पकड़ने में मदद करता है, लेकिन हर पैराग्राफ की पुष्टि या सामान्य HTML वैलिडेशन नहीं करता।

यह क्लाइंट पूरे पेज को मेमोरी में बफ़र करता है। बहुत बड़े दस्तावेज़ों के लिए, स्पष्ट दस्तावेज़ फ़ॉर्मैट और पूर्णता के नियमों के साथ क्रमिक प्रोसेसिंग चुनें। यह न मानें कि हर read में पूरा पैराग्राफ़ मिलता है या वह अपने आप में अर्थपूर्ण अलग रिस्पॉन्स है।

स्टेटस कोड बदलने से पहले पूरा होना जाँचें

समस्या का पता लगाने के लिए यह क्रम अपनाएँ। इससे ट्रांसपोर्ट का पूरा होना और ऐप्लिकेशन की अपेक्षित सामग्री अलग-अलग जाँची जाती है, ताकि डाउनलोड सफल दिखने पर भी कोई छूटा हुआ समापन छिपा न रहे।

नतीजा अगला कदम
स्थानीय कमांड कनेक्ट नहीं कर पाता पुष्टि करें कि सर्वर चल रहा है और पोर्ट मेल खाता है।
Curl का समय समाप्त हो जाता है जाँचें कि डेटा बनाने वाला स्रोत पूरा होता है और end() कॉल करता है; त्रुटि आउटपुट सहेजें।
ट्रांसफ़र पूरा होता है, लेकिन समापन की जाँच विफल रहती है तैयार सामग्री और उस जगह की जाँच करें जहाँ ऐप्लिकेशन रिस्पॉन्स समाप्त करता है।
पूरा दस्तावेज़ एक साथ पहुँचता है कच्चे और डिकोड किए गए आउटपुट की तुलना करें; दिखने के समय के आधार पर कई रिस्पॉन्स होने का निष्कर्ष न निकालें।
सामान्य पूरे पेज के अनुरोध पर 206 मिलता है जाँचें कि रेंज हैंडलिंग अनजाने में लागू तो नहीं हुई; क्रमशः डेटा भेजना अपने आप में इसकी वजह नहीं है।

पूर्णता की ये जाँचें प्रोटोकॉल और Fetch के व्यवहार से निकली सिफ़ारिशें हैं, HTTP की कोई अलग सुविधा नहीं। अगर बॉडी पढ़ने की प्रक्रिया विफल होती है, तो ऊपर दिया क्लाइंट विफलता की सूचना देता है और अब तक मिली सामग्री के लिए सफलता संदेश दिखाने के बजाय असफल स्थिति के साथ बंद होता है।

सार्वजनिक पेज और Google Search के लिए इसका क्या मतलब है?

काम की दस्तावेज़ सामग्री HTML रिस्पॉन्स में ही रखें। Google बताता है कि जिन पेजों के रिस्पॉन्स HTML में सारी सामग्री मौजूद हो, उन्हें क्रॉल और पार्स करना आसान होता है; वह सर्वर-साइड रेंडरिंग या प्रीरेंडरिंग की सलाह देता है, क्योंकि हर bot JavaScript नहीं चलाता। नमूना यही तरीका अपनाता है: उसका टेक्स्ट सीधे दस्तावेज़ की बॉडी में आता है। Google Search Central: Understand the JavaScript SEO basics।

Google के crawler दस्तावेज़ कहते हैं कि 200 रिस्पॉन्स में मिली सामग्री अगले प्रोसेसिंग चरण में भेज दी जाती है; इससे इंडेक्स होने की गारंटी नहीं मिलती। उद्धृत दस्तावेज़ ऐसा कोई खास chunk आकार या देरी नहीं बताते जिससे पेज इंडेक्स होना सुनिश्चित हो। स्ट्रीमिंग को सामग्री पहुँचाने का तरीका समझें और तैयार सामग्री को अलग से जाँचें। Google: How HTTP status codes affect Google's crawlers।

इस प्रदर्शन को सार्वजनिक होस्टिंग के लिए अपनाते समय, असली दस्तावेज़ endpoint पर ये जाँचें दोहराएँ। सफल स्टेटस की पुष्टि करें, पूरी बॉडी पढ़ें और उसकी अंतिम सामग्री को इच्छित पेज से मिलाएँ। स्वीकृति की जाँच इस बात से अलग रखें कि दस्तावेज़ कितनी writes, frames या दिखने वाले अपडेट के ज़रिए पहुँचा।

स्रोत