1'use strict';
2
3const {
4  mustCall,
5  mustNotCall,
6  hasCrypto,
7  platformTimeout,
8  skip
9} = require('../common');
10if (!hasCrypto)
11  skip('missing crypto');
12const { strictEqual } = require('assert');
13const {
14  createServer,
15  connect,
16  constants: {
17    HTTP2_HEADER_STATUS,
18    HTTP_STATUS_OK
19  }
20} = require('http2');
21
22{
23  // Http2ServerResponse.end accepts chunk, encoding, cb as args
24  // It may be invoked repeatedly without throwing errors
25  // but callback will only be called once
26  const server = createServer(mustCall((request, response) => {
27    response.end('end', 'utf8', mustCall(() => {
28      response.end(mustCall());
29      process.nextTick(() => {
30        response.end(mustCall());
31        server.close();
32      });
33    }));
34    response.on('finish', mustCall(() => {
35      response.end(mustCall());
36    }));
37    response.end(mustCall());
38  }));
39  server.listen(0, mustCall(() => {
40    let data = '';
41    const { port } = server.address();
42    const url = `http://localhost:${port}`;
43    const client = connect(url, mustCall(() => {
44      const headers = {
45        ':path': '/',
46        ':method': 'GET',
47        ':scheme': 'http',
48        ':authority': `localhost:${port}`
49      };
50      const request = client.request(headers);
51      request.setEncoding('utf8');
52      request.on('data', (chunk) => (data += chunk));
53      request.on('end', mustCall(() => {
54        strictEqual(data, 'end');
55        client.close();
56      }));
57      request.end();
58      request.resume();
59    }));
60  }));
61}
62
63{
64  // Http2ServerResponse.end should return self after end
65  const server = createServer(mustCall((request, response) => {
66    strictEqual(response, response.end());
67    strictEqual(response, response.end());
68    server.close();
69  }));
70  server.listen(0, mustCall(() => {
71    const { port } = server.address();
72    const url = `http://localhost:${port}`;
73    const client = connect(url, mustCall(() => {
74      const headers = {
75        ':path': '/',
76        ':method': 'GET',
77        ':scheme': 'http',
78        ':authority': `localhost:${port}`
79      };
80      const request = client.request(headers);
81      request.setEncoding('utf8');
82      request.on('end', mustCall(() => {
83        client.close();
84      }));
85      request.end();
86      request.resume();
87    }));
88  }));
89}
90
91{
92  // Http2ServerResponse.end can omit encoding arg, sets it to utf-8
93  const server = createServer(mustCall((request, response) => {
94    response.end('test\uD83D\uDE00', mustCall(() => {
95      server.close();
96    }));
97  }));
98  server.listen(0, mustCall(() => {
99    let data = '';
100    const { port } = server.address();
101    const url = `http://localhost:${port}`;
102    const client = connect(url, mustCall(() => {
103      const headers = {
104        ':path': '/',
105        ':method': 'GET',
106        ':scheme': 'http',
107        ':authority': `localhost:${port}`
108      };
109      const request = client.request(headers);
110      request.setEncoding('utf8');
111      request.on('data', (chunk) => (data += chunk));
112      request.on('end', mustCall(() => {
113        strictEqual(data, 'test\uD83D\uDE00');
114        client.close();
115      }));
116      request.end();
117      request.resume();
118    }));
119  }));
120}
121
122{
123  // Http2ServerResponse.end can omit chunk & encoding args
124  const server = createServer(mustCall((request, response) => {
125    response.end(mustCall(() => {
126      server.close();
127    }));
128  }));
129  server.listen(0, mustCall(() => {
130    const { port } = server.address();
131    const url = `http://localhost:${port}`;
132    const client = connect(url, mustCall(() => {
133      const headers = {
134        ':path': '/',
135        ':method': 'GET',
136        ':scheme': 'http',
137        ':authority': `localhost:${port}`
138      };
139      const request = client.request(headers);
140      request.on('data', mustNotCall());
141      request.on('end', mustCall(() => client.close()));
142      request.end();
143      request.resume();
144    }));
145  }));
146}
147
148{
149  // Http2ServerResponse.end is necessary on HEAD requests in compat
150  // for http1 compatibility
151  const server = createServer(mustCall((request, response) => {
152    strictEqual(response.writableEnded, false);
153    strictEqual(response.finished, false);
154    response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
155    strictEqual(response.finished, false);
156    response.end('data', mustCall());
157    strictEqual(response.writableEnded, true);
158    strictEqual(response.finished, true);
159  }));
160  server.listen(0, mustCall(() => {
161    const { port } = server.address();
162    const url = `http://localhost:${port}`;
163    const client = connect(url, mustCall(() => {
164      const headers = {
165        ':path': '/',
166        ':method': 'HEAD',
167        ':scheme': 'http',
168        ':authority': `localhost:${port}`
169      };
170      const request = client.request(headers);
171      request.on('response', mustCall((headers, flags) => {
172        strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
173        strictEqual(flags, 5); // The end of stream flag is set
174        strictEqual(headers.foo, 'bar');
175      }));
176      request.on('data', mustNotCall());
177      request.on('end', mustCall(() => {
178        client.close();
179        server.close();
180      }));
181      request.end();
182      request.resume();
183    }));
184  }));
185}
186
187{
188  // .end should trigger 'end' event on request if user did not attempt
189  // to read from the request
190  const server = createServer(mustCall((request, response) => {
191    request.on('end', mustCall());
192    response.end();
193  }));
194  server.listen(0, mustCall(() => {
195    const { port } = server.address();
196    const url = `http://localhost:${port}`;
197    const client = connect(url, mustCall(() => {
198      const headers = {
199        ':path': '/',
200        ':method': 'HEAD',
201        ':scheme': 'http',
202        ':authority': `localhost:${port}`
203      };
204      const request = client.request(headers);
205      request.on('data', mustNotCall());
206      request.on('end', mustCall(() => {
207        client.close();
208        server.close();
209      }));
210      request.end();
211      request.resume();
212    }));
213  }));
214}
215
216
217{
218  // Should be able to call .end with cb from stream 'close'
219  const server = createServer(mustCall((request, response) => {
220    response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
221    response.stream.on('close', mustCall(() => {
222      response.end(mustCall());
223    }));
224  }));
225  server.listen(0, mustCall(() => {
226    const { port } = server.address();
227    const url = `http://localhost:${port}`;
228    const client = connect(url, mustCall(() => {
229      const headers = {
230        ':path': '/',
231        ':method': 'HEAD',
232        ':scheme': 'http',
233        ':authority': `localhost:${port}`
234      };
235      const request = client.request(headers);
236      request.on('response', mustCall((headers, flags) => {
237        strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
238        strictEqual(flags, 5); // The end of stream flag is set
239        strictEqual(headers.foo, 'bar');
240      }));
241      request.on('data', mustNotCall());
242      request.on('end', mustCall(() => {
243        client.close();
244        server.close();
245      }));
246      request.end();
247      request.resume();
248    }));
249  }));
250}
251
252{
253  // Should be able to respond to HEAD request after timeout
254  const server = createServer(mustCall((request, response) => {
255    setTimeout(mustCall(() => {
256      response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
257      response.end('data', mustCall());
258    }), platformTimeout(10));
259  }));
260  server.listen(0, mustCall(() => {
261    const { port } = server.address();
262    const url = `http://localhost:${port}`;
263    const client = connect(url, mustCall(() => {
264      const headers = {
265        ':path': '/',
266        ':method': 'HEAD',
267        ':scheme': 'http',
268        ':authority': `localhost:${port}`
269      };
270      const request = client.request(headers);
271      request.on('response', mustCall((headers, flags) => {
272        strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
273        strictEqual(flags, 5); // The end of stream flag is set
274        strictEqual(headers.foo, 'bar');
275      }));
276      request.on('data', mustNotCall());
277      request.on('end', mustCall(() => {
278        client.close();
279        server.close();
280      }));
281      request.end();
282      request.resume();
283    }));
284  }));
285}
286
287{
288  // Finish should only trigger after 'end' is called
289  const server = createServer(mustCall((request, response) => {
290    let finished = false;
291    response.writeHead(HTTP_STATUS_OK, { foo: 'bar' });
292    response.on('finish', mustCall(() => {
293      finished = false;
294    }));
295    response.end('data', mustCall(() => {
296      strictEqual(finished, false);
297      response.end('data', mustCall());
298    }));
299  }));
300  server.listen(0, mustCall(() => {
301    const { port } = server.address();
302    const url = `http://localhost:${port}`;
303    const client = connect(url, mustCall(() => {
304      const headers = {
305        ':path': '/',
306        ':method': 'HEAD',
307        ':scheme': 'http',
308        ':authority': `localhost:${port}`
309      };
310      const request = client.request(headers);
311      request.on('response', mustCall((headers, flags) => {
312        strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
313        strictEqual(flags, 5); // The end of stream flag is set
314        strictEqual(headers.foo, 'bar');
315      }));
316      request.on('data', mustNotCall());
317      request.on('end', mustCall(() => {
318        client.close();
319        server.close();
320      }));
321      request.end();
322      request.resume();
323    }));
324  }));
325}
326
327{
328  // Should be able to respond to HEAD with just .end
329  const server = createServer(mustCall((request, response) => {
330    response.end('data', mustCall());
331    response.end(mustCall());
332  }));
333  server.listen(0, mustCall(() => {
334    const { port } = server.address();
335    const url = `http://localhost:${port}`;
336    const client = connect(url, mustCall(() => {
337      const headers = {
338        ':path': '/',
339        ':method': 'HEAD',
340        ':scheme': 'http',
341        ':authority': `localhost:${port}`
342      };
343      const request = client.request(headers);
344      request.on('response', mustCall((headers, flags) => {
345        strictEqual(headers[HTTP2_HEADER_STATUS], HTTP_STATUS_OK);
346        strictEqual(flags, 5); // The end of stream flag is set
347      }));
348      request.on('data', mustNotCall());
349      request.on('end', mustCall(() => {
350        client.close();
351        server.close();
352      }));
353      request.end();
354      request.resume();
355    }));
356  }));
357}
358