summaryrefslogtreecommitdiffstats
path: root/test/fetch/http2.js
diff options
context:
space:
mode:
Diffstat (limited to 'test/fetch/http2.js')
-rw-r--r--test/fetch/http2.js415
1 files changed, 415 insertions, 0 deletions
diff --git a/test/fetch/http2.js b/test/fetch/http2.js
new file mode 100644
index 0000000..9f6997f
--- /dev/null
+++ b/test/fetch/http2.js
@@ -0,0 +1,415 @@
+'use strict'
+
+const { createSecureServer } = require('node:http2')
+const { createReadStream, readFileSync } = require('node:fs')
+const { once } = require('node:events')
+const { Blob } = require('node:buffer')
+const { Readable } = require('node:stream')
+
+const { test, plan } = require('tap')
+const pem = require('https-pem')
+
+const { Client, fetch, Headers } = require('../..')
+
+const nodeVersion = Number(process.version.split('v')[1].split('.')[0])
+
+plan(7)
+
+test('[Fetch] Issue#2311', async t => {
+ const expectedBody = 'hello from client!'
+
+ const server = createSecureServer(pem, async (req, res) => {
+ let body = ''
+
+ req.setEncoding('utf8')
+
+ res.writeHead(200, {
+ 'content-type': 'text/plain; charset=utf-8',
+ 'x-custom-h2': req.headers['x-my-header']
+ })
+
+ for await (const chunk of req) {
+ body += chunk
+ }
+
+ res.end(body)
+ })
+
+ t.plan(1)
+
+ server.listen()
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ method: 'POST',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ },
+ body: expectedBody
+ }
+ )
+
+ const responseBody = await response.text()
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ t.equal(responseBody, expectedBody)
+})
+
+test('[Fetch] Simple GET with h2', async t => {
+ const server = createSecureServer(pem)
+ const expectedRequestBody = 'hello h2!'
+
+ server.on('stream', async (stream, headers) => {
+ stream.respond({
+ 'content-type': 'text/plain; charset=utf-8',
+ 'x-custom-h2': headers['x-my-header'],
+ 'x-method': headers[':method'],
+ ':status': 200
+ })
+
+ stream.end(expectedRequestBody)
+ })
+
+ t.plan(5)
+
+ server.listen()
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ method: 'GET',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ }
+ }
+ )
+
+ const responseBody = await response.text()
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ t.equal(responseBody, expectedRequestBody)
+ t.equal(response.headers.get('x-method'), 'GET')
+ t.equal(response.headers.get('x-custom-h2'), 'foo')
+ // https://github.com/nodejs/undici/issues/2415
+ t.throws(() => {
+ response.headers.get(':status')
+ }, TypeError)
+
+ // See https://fetch.spec.whatwg.org/#concept-response-status-message
+ t.equal(response.statusText, '')
+})
+
+test('[Fetch] Should handle h2 request with body (string or buffer)', async t => {
+ const server = createSecureServer(pem)
+ const expectedBody = 'hello from client!'
+ const expectedRequestBody = 'hello h2!'
+ const requestBody = []
+
+ server.on('stream', async (stream, headers) => {
+ stream.on('data', chunk => requestBody.push(chunk))
+
+ stream.respond({
+ 'content-type': 'text/plain; charset=utf-8',
+ 'x-custom-h2': headers['x-my-header'],
+ ':status': 200
+ })
+
+ stream.end(expectedRequestBody)
+ })
+
+ t.plan(2)
+
+ server.listen()
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ method: 'POST',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ },
+ body: expectedBody
+ }
+ )
+
+ const responseBody = await response.text()
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ t.equal(Buffer.concat(requestBody).toString('utf-8'), expectedBody)
+ t.equal(responseBody, expectedRequestBody)
+})
+
+// Skipping for now, there is something odd in the way the body is handled
+test(
+ '[Fetch] Should handle h2 request with body (stream)',
+ { skip: nodeVersion === 16 },
+ async t => {
+ const server = createSecureServer(pem)
+ const expectedBody = readFileSync(__filename, 'utf-8')
+ const stream = createReadStream(__filename)
+ const requestChunks = []
+
+ server.on('stream', async (stream, headers) => {
+ t.equal(headers[':method'], 'PUT')
+ t.equal(headers[':path'], '/')
+ t.equal(headers[':scheme'], 'https')
+
+ stream.respond({
+ 'content-type': 'text/plain; charset=utf-8',
+ 'x-custom-h2': headers['x-my-header'],
+ ':status': 200
+ })
+
+ for await (const chunk of stream) {
+ requestChunks.push(chunk)
+ }
+
+ stream.end('hello h2!')
+ })
+
+ t.plan(8)
+
+ server.listen(0)
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ method: 'PUT',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ },
+ body: Readable.toWeb(stream),
+ duplex: 'half'
+ }
+ )
+
+ const responseBody = await response.text()
+
+ t.equal(response.status, 200)
+ t.equal(response.headers.get('content-type'), 'text/plain; charset=utf-8')
+ t.equal(response.headers.get('x-custom-h2'), 'foo')
+ t.equal(responseBody, 'hello h2!')
+ t.equal(Buffer.concat(requestChunks).toString('utf-8'), expectedBody)
+ }
+)
+test('Should handle h2 request with body (Blob)', { skip: !Blob }, async t => {
+ const server = createSecureServer(pem)
+ const expectedBody = 'asd'
+ const requestChunks = []
+ const body = new Blob(['asd'], {
+ type: 'text/plain'
+ })
+
+ server.on('stream', async (stream, headers) => {
+ t.equal(headers[':method'], 'POST')
+ t.equal(headers[':path'], '/')
+ t.equal(headers[':scheme'], 'https')
+
+ stream.on('data', chunk => requestChunks.push(chunk))
+
+ stream.respond({
+ 'content-type': 'text/plain; charset=utf-8',
+ 'x-custom-h2': headers['x-my-header'],
+ ':status': 200
+ })
+
+ stream.end('hello h2!')
+ })
+
+ t.plan(8)
+
+ server.listen(0)
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ body,
+ method: 'POST',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ }
+ }
+ )
+
+ const responseBody = await response.arrayBuffer()
+
+ t.equal(response.status, 200)
+ t.equal(response.headers.get('content-type'), 'text/plain; charset=utf-8')
+ t.equal(response.headers.get('x-custom-h2'), 'foo')
+ t.same(new TextDecoder().decode(responseBody).toString(), 'hello h2!')
+ t.equal(Buffer.concat(requestChunks).toString('utf-8'), expectedBody)
+})
+
+test(
+ 'Should handle h2 request with body (Blob:ArrayBuffer)',
+ { skip: !Blob },
+ async t => {
+ const server = createSecureServer(pem)
+ const expectedBody = 'hello'
+ const requestChunks = []
+ const expectedResponseBody = { hello: 'h2' }
+ const buf = Buffer.from(expectedBody)
+ const body = new ArrayBuffer(buf.byteLength)
+
+ buf.copy(new Uint8Array(body))
+
+ server.on('stream', async (stream, headers) => {
+ t.equal(headers[':method'], 'PUT')
+ t.equal(headers[':path'], '/')
+ t.equal(headers[':scheme'], 'https')
+
+ stream.on('data', chunk => requestChunks.push(chunk))
+
+ stream.respond({
+ 'content-type': 'application/json',
+ 'x-custom-h2': headers['x-my-header'],
+ ':status': 200
+ })
+
+ stream.end(JSON.stringify(expectedResponseBody))
+ })
+
+ t.plan(8)
+
+ server.listen(0)
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ body,
+ method: 'PUT',
+ dispatcher: client,
+ headers: {
+ 'x-my-header': 'foo',
+ 'content-type': 'text-plain'
+ }
+ }
+ )
+
+ const responseBody = await response.json()
+
+ t.equal(response.status, 200)
+ t.equal(response.headers.get('content-type'), 'application/json')
+ t.equal(response.headers.get('x-custom-h2'), 'foo')
+ t.same(responseBody, expectedResponseBody)
+ t.equal(Buffer.concat(requestChunks).toString('utf-8'), expectedBody)
+ }
+)
+
+test('Issue#2415', async (t) => {
+ t.plan(1)
+ const server = createSecureServer(pem)
+
+ server.on('stream', async (stream, headers) => {
+ stream.respond({
+ ':status': 200
+ })
+ stream.end('test')
+ })
+
+ server.listen()
+ await once(server, 'listening')
+
+ const client = new Client(`https://localhost:${server.address().port}`, {
+ connect: {
+ rejectUnauthorized: false
+ },
+ allowH2: true
+ })
+
+ const response = await fetch(
+ `https://localhost:${server.address().port}/`,
+ // Needs to be passed to disable the reject unauthorized
+ {
+ method: 'GET',
+ dispatcher: client
+ }
+ )
+
+ await response.text()
+
+ t.teardown(server.close.bind(server))
+ t.teardown(client.close.bind(client))
+
+ t.doesNotThrow(() => new Headers(response.headers))
+})