4

I'm trying to figure out how to send an HTTP/2 POST request with NodeJS. I have so far from the example in the documentation:

const http2 = require('http2');
const fs = require('fs');
const client = http2.connect('https://localhost:8443', {
  ca: fs.readFileSync('localhost-cert.pem')
});
client.on('error', (err) => console.error(err));
client.on('socketError', (err) => console.error(err));

const req = client.request({ ':path': '/' });

req.on('response', (headers, flags) => {
  for (const name in headers) {
    console.log(`${name}: ${headers[name]}`);
  }
});

req.setEncoding('utf8');
let data = '';
req.on('data', (chunk) => { data += chunk; });
req.on('end', () => {
  console.log(`\n${data}`);
  client.close();
});
req.end();

But there it isn't clear to me how to actually setup data to send as a POST.

John Smith
  • 3,493
  • 3
  • 25
  • 52

3 Answers3

16

If you need to post your objects as json - you should stringify it and wrap in buffer. Here is the code that works in my case:

const http2 = require('http2');

const post = (url, path, body) => new Promise((resolve) => {
    const client = http2.connect(url);

    const buffer = Buffer.from(JSON.stringify(body));

    const req = client.request({
        [http2.constants.HTTP2_HEADER_SCHEME]: "https",
        [http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_POST,
        [http2.constants.HTTP2_HEADER_PATH]: `/${path}`,
        "Content-Type": "application/json",
        "Content-Length": buffer.length,
    });

    req.setEncoding('utf8');
    let data = [];
    req.on('data', (chunk) => {
        data.push(chunk);
    });
    req.write(buffer);
    req.end();
    req.on('end', () => {
        resolve({ data: data.join("") });
    });
});
Xavi
  • 20,111
  • 14
  • 72
  • 63
  • 1
    The above example works for me as long as I change the join to include an empty string delimiter: `data.join("")`. Otherwise It ends up adding extra commas to the result. – Schuyler Cumbie Jan 18 '19 at 17:00
6

After piecing together little bits of information everywhere, I've finally manage to resolve this issue. Here is a template example. The key is in req.write(). I honestly couldn't find anywhere a direct answer for how to include a body. Nearly every example is without a body! Hope this helps others. NOTE: This is in Node-red hence the global.get statements, but also works by changing those to require('module'):

const fs = global.get('fs');
const http2 = global.get('http2');

fs.readFile('turn-off-the-desklight.raw', function(err, content){
    if(err){
        node.send(err);
    }
    var metadata = JSON.stringify(
    {  
        "context": [   
            {
                "header": {
                    "namespace": "SpeechRecognizer",
                    "name": "RecognizerState"
                },
                "payload": {

                }
            },
            {
                "header": {
                    "namespace": "Speaker",
                    "name": "VolumeState"
                },
                "payload": {
                    "volume": 10,
                    "muted": false
                }
            },
            {
                "header": {
                    "namespace": "Alerts",
                    "name": "AlertsState"
                },
                "payload": {
                    "allAlerts": [],
                    "activeAlerts": []
                }
            },
            {
                "header": {
                    "namespace": "SpeechSynthesizer",
                    "name": "SpeechState"
                },
                "payload": {
                    "token": "",
                    "offsetInMilliseconds": 0,
                    "playerActivity": "FINISHED"
                }
            },
            {
                "header": {
                    "namespace": "AudioPlayer",
                    "name": "PlaybackState"
                },
                "payload": {
                    "token": "",
                    "offsetInMilliseconds": 0,
                    "playerActivity": "IDLE"
                }
            }
        ],  
        "event": {  
            "header": {  
                "namespace": "SpeechRecognizer",  
                "name": "Recognize",  
                "messageId": "1eff3c5e-02e3-4dd3-9ca0-7c38937f005f",  
                "dialogRequestId": "a905c2bb-1bbd-45cf-9f85-6563d2546492"
            },  
            "payload": {  
                "profile": "FAR_FIELD",
                "format": "AUDIO_L16_RATE_16000_CHANNELS_1"
            }  
        }  
    });
    var data = "--this-is-my-boundary-for-alexa\r\n";
    data += 'Content-Disposition: form-data; name="metadata"\r\n';
    data +='Content-Type: application/json; charset=UTF-8\r\n\r\n';
    data += metadata;
    data += "\r\n";
    data += "--this-is-my-boundary-for-alexa\r\n";
    data += "Content-Disposition: form-data; name=\"audio\"\r\n";
    data += "Content-Type:application/octet-stream\r\n\r\n";
    var payload = Buffer.concat([
            Buffer.from(data, "utf8"),
            new Buffer(content, 'binary'),
            Buffer.from("\r\n--this-is-my-boundary-for-alexa\r\n", "utf8"),
    ]);

    const client = global.get('alexaClient');

    client.on('error', (err) => node.send({payload:err}));
    client.on('socketError', (err) => node.send({payload:err}));

    var request = {
        ':method' : 'POST',  
        ':scheme' : 'https',  
        ':path' : '/v20160207/events',
        'authorization' : 'Bearer <valid token>',
        'content-type' : 'multipart/form-data; boundary=this-is-my-boundary-for-alexa'
    };

    var req = client.request(request);

    req.on('response', (headers, flags) => {
        for (const name in headers) {
            if(name === ':status') {
                node.send({payload:`${name}: ${headers[name]}`});
            }
        }
    });

    req.on('error', function(err) {
      node.send(err);
    });

    req.setEncoding('utf8');
    let outdata = '';
    req.on('data', (chunk) => { outdata += chunk; });
    req.on('end', () => {
        node.send({payload:outdata});
    });

    req.write(payload);

    req.end();  
}); 
John Smith
  • 3,493
  • 3
  • 25
  • 52
  • No matter what I do, after req.write, and req.end I am not seeing data get sent out to the server. The server is even indicating the buffer is null on the other side. – Nick Mar 30 '18 at 22:12
  • 1
    @Nick Open another question and post your code and then link back here. I'll have a look. – John Smith Mar 31 '18 at 02:01
  • thanks here it is https://stackoverflow.com/questions/49583948/sending-http2-request-to-grpc-server-node-illegal-buffer – Nick Mar 31 '18 at 03:31
  • @JohnSmith thanks for the code.. were you able to save the audio to local disk? I want to convert the response from speech to text. – Rajesh Nov 09 '18 at 11:21
0

You can send data to server and receive data from server.

1) Client

const http2 = require('http2');
var port = 15000;
const client = http2.connect('https://localhost:15000/');

// Must not specify the ':path' and ':scheme' headers
// for CONNECT requests or an error will be thrown.
var body = "Hello, I am babatman";
const req = client.request({
  ':method': 'POST',
  ':authority': `localhost:${port}`, 
  'body': body
});
req.on('response', (headers) => {
  console.log(headers[http2.constants.HTTP2_HEADER_STATUS]);
});
let data = '';
req.setEncoding('utf8');
req.on('data', (chunk) => data += chunk);
req.on('end', () => {
  console.log(`The server says: ${data}`);
  client.close();
});

2) Server

app.use(async ctx => {
console.log(ctx.request);

ctx.response.status = 201;
ctx.body = "hello";
console.log("Response 201 was send.")
});
Deniz Babat
  • 205
  • 2
  • 6