@@ -10,7 +10,7 @@ const { Writable, pipeline, PassThrough, Readable } = require('node:stream')
1010
1111const pem = require ( 'https-pem' )
1212
13- const { Client, Agent } = require ( '..' )
13+ const { Client, Agent, FormData } = require ( '..' )
1414
1515const isGreaterThanv20 = process . versions . node . split ( '.' ) . map ( Number ) [ 0 ] >= 20
1616
@@ -1450,3 +1450,129 @@ test('#3671 - Graceful close', async (t) => {
14501450
14511451 await t . completed
14521452} )
1453+
1454+ test ( '#3753 - Handle GOAWAY Gracefully' , async ( t ) => {
1455+ const server = createSecureServer ( pem )
1456+ let counter = 0
1457+ let session = null
1458+
1459+ server . on ( 'session' , s => {
1460+ session = s
1461+ } )
1462+
1463+ server . on ( 'stream' , ( stream ) => {
1464+ counter ++
1465+
1466+ // Due to the nature of the test, we need to ignore the error
1467+ // that is thrown when the session is destroyed and stream
1468+ // is in-flight
1469+ stream . on ( 'error' , ( ) => { } )
1470+ if ( counter === 9 && session != null ) {
1471+ session . goaway ( )
1472+ stream . end ( )
1473+ } else {
1474+ stream . respond ( {
1475+ 'content-type' : 'text/plain' ,
1476+ ':status' : 200
1477+ } )
1478+ setTimeout ( ( ) => {
1479+ stream . end ( 'hello world' )
1480+ } , 150 )
1481+ }
1482+ } )
1483+
1484+ server . listen ( 0 )
1485+ await once ( server , 'listening' )
1486+
1487+ const client = new Client ( `https://localhost:${ server . address ( ) . port } ` , {
1488+ connect : {
1489+ rejectUnauthorized : false
1490+ } ,
1491+ pipelining : 2 ,
1492+ allowH2 : true
1493+ } )
1494+
1495+ t = tspl ( t , { plan : 30 } )
1496+ after ( ( ) => client . close ( ) )
1497+ after ( ( ) => server . close ( ) )
1498+
1499+ for ( let i = 0 ; i < 15 ; i ++ ) {
1500+ client . request ( {
1501+ path : '/' ,
1502+ method : 'GET' ,
1503+ headers : {
1504+ 'x-my-header' : 'foo'
1505+ }
1506+ } , ( err , response ) => {
1507+ if ( err ) {
1508+ t . strictEqual ( err . message , 'HTTP/2: "GOAWAY" frame received with code 0' )
1509+ t . strictEqual ( err . code , 'UND_ERR_SOCKET' )
1510+ } else {
1511+ t . strictEqual ( response . statusCode , 200 )
1512+ ; ( async function ( ) {
1513+ let body
1514+ try {
1515+ body = await response . body . text ( )
1516+ } catch ( err ) {
1517+ t . strictEqual ( err . code , 'UND_ERR_SOCKET' )
1518+ return
1519+ }
1520+ t . strictEqual ( body , 'hello world' )
1521+ } ) ( )
1522+ }
1523+ } )
1524+ }
1525+
1526+ await t . completed
1527+ } )
1528+
1529+ test ( '#3803 - sending FormData bodies works' , async ( t ) => {
1530+ const assert = tspl ( t , { plan : 4 } )
1531+
1532+ const server = createSecureServer ( pem ) . listen ( 0 )
1533+ server . on ( 'stream' , async ( stream , headers ) => {
1534+ const contentLength = Number ( headers [ 'content-length' ] )
1535+
1536+ assert . ok ( ! Number . isNaN ( contentLength ) )
1537+ assert . ok ( headers [ 'content-type' ] ?. startsWith ( 'multipart/form-data; boundary=' ) )
1538+
1539+ stream . respond ( { ':status' : 200 } )
1540+
1541+ const fd = await new Response ( stream , {
1542+ headers : {
1543+ 'content-type' : headers [ 'content-type' ]
1544+ }
1545+ } ) . formData ( )
1546+
1547+ assert . deepEqual ( fd . get ( 'a' ) , 'b' )
1548+ assert . deepEqual ( fd . get ( 'c' ) . name , 'e.fgh' )
1549+
1550+ stream . end ( )
1551+ } )
1552+
1553+ await once ( server , 'listening' )
1554+
1555+ const client = new Client ( `https://localhost:${ server . address ( ) . port } ` , {
1556+ connect : {
1557+ rejectUnauthorized : false
1558+ } ,
1559+ allowH2 : true
1560+ } )
1561+
1562+ t . after ( async ( ) => {
1563+ server . close ( )
1564+ await client . close ( )
1565+ } )
1566+
1567+ const fd = new FormData ( )
1568+ fd . set ( 'a' , 'b' )
1569+ fd . set ( 'c' , new Blob ( [ 'd' ] ) , 'e.fgh' )
1570+
1571+ await client . request ( {
1572+ path : '/' ,
1573+ method : 'POST' ,
1574+ body : fd
1575+ } )
1576+
1577+ await assert . completed
1578+ } )
0 commit comments