Fatma Zaman
Fatma Zaman

Reputation: 69

Error: Can't set headers after they are sent for HTTP node server

I'm trying to pass multiple value into server according to the url value.

As I have results value like below

[ { url: 'account/41',

    status: '200',

    headers:

     [ 'content-type = application/json,',

       'content-type = application/text' ],

    body: [ '{ name: XYZ }' ] },

  { url: 'account/43',

    status: '201',

    headers: [ 'content-type = application/xml']

    body: [ '{ name : ZYX }' ] } ]

here, I'm trying to create a server request/response for above multiple value. I used forEach loop to iterate over like if url = account/41 then body will return [ '{ name: XYZ }' ] } and if url = account/43 then body will return [ '{ name : ZYX }' ]. but it's only taking first element of the results value and giving me below error:

UnhandledPromiseRejectionWarning: Error: Can't set headers after they are sent.

something I'm missing but not sure what ? How can I pass one by one value with proper output.

const http = require('http')

const parse = require('./Parse.js')



// create a server object:

let server = http.createServer(function(request, response) {

    parse.allFiles().then( results => {

        results.forEach(element => {

            if (element.url.includes(request.url) ) {

            let headerSplit = ( element.headers + '' ).split('=')

            let headername = headerSplit[0]

            let headerVal = headerSplit[1]

            response.setHeader( headername, headerVal )

            response.write( JSON.stringify( element.body ) )

            response.statuscode = parseInt( element.status )

            response.end()

        } else {

            response.writeHead(404, {'Content-Type': 'text/html'})

            response.end('No Page Found')

        }

        })

    })

})



const port = process.env.PORT || 8080



server.listen(port, function() {

// eslint-disable-next-line no-console

console.log('Listening on port ' + port)

})

I have multiple file which carries own URL, STATUS, HEADERS and BODY . After parsing those values(used Parse function) in a single array I'm getting into the variable called results.

[{"url":"account/41","status":"200","headers":["content-type = application/json,"],"body":["{ name: xyz}"]},
{"url":"account/43","status":"201","headers":["content-type = application/xml"],"body":["{ name : zyx}"]}]

If you iterate over the result you will get the element like {"url":"account/41","status":"200","headers":["content-type = application/json"],"body":["{ name: xyz}"]} and in response if you pass the url i the server like localhost:8080/account/41 , it will return ["{ name : zyx}"]}] as response body.

Upvotes: 0

Views: 89

Answers (1)

jfriend00
jfriend00

Reputation: 707148

You have a response.end() inside a .forEach() loop. That means you're calling it multiple times. You can only call it once for each request because when you call it, headers are written, all write buffers are flushed and the request is ended (e.g. socket closed). Thus, you can't "end" the request multiple times. Since your results.forEach() loop is synchronous, it appears you could perhaps put the response.end() somewhere after the loop is done so it is only called once when all the response.write() calls in the loop are done.

You will also have to fix something else in your loop because you appear to be deciding somewhere in the middle of the loop that you're going to return a 404 if one of the items in the loop doesn't work. I don't know what logic you really should have there. Perhaps you should skip any item that does pass your if test and only send a 404 if none matched?

You're also trying to set the status and a particular header over and over inside the loop. Those can only have one value so it isn't clear why you're setting it over and over inside the loop. Only the last iteration of the loop will stick.

So, your logic about how to process the loop is really just flawed. We'd have to know more about what the data looks like and what you want the response to look like for us to know how to rework your loop to work properly.

List of things to fix:

  1. Can only call response.end() once per request.
  2. response.statuscode can only have one value so it is unclear why you're calling it multiple times in the loop.
  3. You decide on a 404 status inside the loop, but then the loop continues trying to write other values. That design doesn't really make sense. Perhaps you need to decide after you've traversed the loop if you should send a 404 or not based on what you found in the loop.
  4. Your server is calling this code for every single request that it receives. Hopefully that is just because you stripped a bunch of code out when posting because you should never be doing that. You should have a specific path that you are checking for and do all this work only when that path is requested. Otherwise, you'd be doing all this work when crawlers were probing your site, when browsers ask for favicons, etc...

You still haven't really explained what response you want to construct, but I'll take a guess. If you find element.url.includes(request.url), then you want to send that response and stop the loop and be done. If you don't find element.url.includes(request.url) in any one of the results, then you want to send a 404. If that's the case, you can do that like this:

const server = http.createServer(function(request, response) {
    parse.allFiles().then(results => {
        // find first element.url that matches our request.url
        const match = results.find(element => element.url.includes(request.url));
        if (match) {
            let headerSplit = (match.headers + '').split('=');
            let headername = headerSplit[0];
            let headerVal = headerSplit[1];
            response.setHeader(headername, headerVal);
            response.write(JSON.stringify(match.body));
            response.statuscode = parseInt(match.status);
            response.end()
        } else {
            response.writeHead(404, {
                'Content-Type': 'text/plain'
            })
            response.end('No Page Found')
        }
    });
});

Upvotes: 3

Related Questions