Serving image file with Buffers - corrupted image - node.js

I'm trying to get into Node.js, I'm following some tutorials and I'm now stuck on an example that explains how to create a simple web server to serve static files, using Buffers.
Here is the relevant code:
function serveStaticFile(file, res){
var rs = fs.createReadStream(file);
var ct = contentTypeForFile(file);
res.writeHead(200, { "Content-Type" : ct });
rs.on('readable', function(){
var d = rs.read();
if (d) {
if ( typeof d == 'string' )
res.write(d);
else if ( typeof d == 'object' && d instanceof Buffer )
res.write(d.toString('utf8'));
}
});
rs.on('error', function(e){
res.writeHead(404, { "Content-Type" : "application/json" });
var out = { error : "not_found", message : "'" + file + "' not found" };
res.end(JSON.stringify(out) + "\n");
});
rs.on('end', function(){
res.end();
});
}
the function serveStaticFile runs on incoming requests, file is the filename of the file you want to retrieve.
This is contentTypeForFile():
function contentTypeForFile(file){
var ext = path.extname(file);
switch(ext){
case '.html': return "text/html";
case '.js' : return "text/javascript";
case '.css' : return "text/css";
case '.jpg' :
case '.jpeg' : return "image/jpeg";
default : return "text/plain";
}
}
Running this command curl -o test.jpg http://localhost:8000/photo.jpg in the cmd actually downloads and creates a test.jpg file in my folder, the problem is that when I try to open the image the image itself seems to be corrupted.
What am I doing wrong? How can I convert the d.toString('utf8') back to an actual image once it is downloaded?
EDIT: I think the problem is that the response headers are saved toghether with the file content itself.. if I open the image file with a text editor, this is the content:
HTTP/1.1 200 OK
Content-Type: image/jpeg
Date: Sat, 30 Aug 2014 00:31:58 GMT
Connection: keep-alive
Transfer-Encoding: chunked
���� [ ... ]
How can I get rid of the headers while saving the file?
Thanks in advance, regards

Just remove the .toString('utf8'), you don't want or need it there. As a matter of fact, you can probably change this:
if (d) {
if ( typeof d == 'string' )
res.write(d);
else if ( typeof d == 'object' && d instanceof Buffer )
res.write(d.toString('utf8'));
}
To simply:
if (d) {
res.write(d);
}
res.write already handles checking the type of data you're passing to it and encoding it properly. The only time you need to worry about it is if you send it a string that is not utf8 encoded. From the docs:
This sends a chunk of the response body. This method may be called
multiple times to provide successive parts of the body.
chunk can be a string or a buffer. If chunk is a string, the second
parameter specifies how to encode it into a byte stream. By default
the encoding is utf8.
I just re-read second part of your question and realized that you said that you found headers in the test.jpg file when you opened it with a text editor. The only way I know of that that can happen when using curl is by specifying the -i option. Something like: curl -i -o test.jpg http://localhost:8000/photo.jpg. For the most part, I wouldn't recommend using that option with curl since it will corrupt any binary files it downloads. There are certainly use cases for it, but this isn't one of them.
Regardless of what was going on with curl, it's important to know that the server code has nothing to do with what the client side does with the headers it receives. Once the server sends the headers (and the content for that matter), it's completely out of its hands. I can not think of any way the code on the server could force the client to save headers in a file along with the actual content; that's completely up to the client.
Note: If you want to see the headers curl is receiving (and sending for that matter), try the -v option. Received headers will be prefixed with < and sent with >; lines prefixed with * have to do with the connection itself. It looks something like this:
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8000
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 30 Aug 2014 15:54:14 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
[data not shown]

Related

Why does the request pipe includes the headers in this code?

I have a strange situation regarding http server and piping request.
From my past experience, when piping the request object of a http server to a writable stream of some sort, it does not include the headers, just the payload.
Today however, I wrote some very simple code, and from some reason, I'm spending the past 2 hours trying to figure out why it writes the headers to the file (super confusing!)
Here's my code:
server = http.createServer((req, res) => {
f = '/tmp/dest'
console.log(`writing to ${f}`)
s = fs.createWriteStream(f)
req.pipe(s)
req.on('end', () => {
res.end("done")
})
})
server.listen(port)
I test this with the following curl command:
curl -XPOST -F 'data=#test.txt' localhost:8080
And this is what I'm getting when I'm reading /tmp/dest:
--------------------------993d19e02b7578ff
Content-Disposition: form-data; name="data"; filename="test.txt"
Content-Type: text/plain
hello - this is some text
--------------------------993d19e02b7578ff--
Why am I seeing the headers here? I expected it to only write the payload
I have a code I wrote about a year ago that streams directly to a file without the headers, I don't understand what's different, but this one did the trick:
imageRouter.post('/upload', async(req, res) => {
if(!req.is("image/*")) {
let errorMessage = `the /upload destination was hit, but content-type is ${req.get("Content-Type")}`;
console.log(errorMessage);
res.status(415).send(errorMessage);
return;
}
let imageType = req.get("Content-Type").split('/')[1];
let [ err, writeStream ] = await getWritableStream({ suffix: imageType });
if (err) {
console.log("error while trying to write", err);
return res.status(500).end();
}
let imageName = writeStream.getID();
req.on('end', () => {
req.unpipe();
writeStream.close();
res.json({
imageRelativeLink: `/images/${imageName}`,
imageFullLink: `${self_hostname}/images/${imageName}`
});
});
req.pipe(writeStream);
});
What's different? Why does my code from a year ago (last block) writes without the form-data/headers? The resulting file is only an image, without text, but this time (the first block) shows http headers in the resulting file
Instead of using pipe, try using on('data') and referring to req.data to pull off the contents. This will allow the http library to process the HTTP body format and handle the "headers" (really: form part descriptors) for you.
Node Streaming Consumer API
server = http.createServer((req, res) => {
f = '/tmp/dest'
console.log(`writing to ${f}`)
s = fs.createWriteStream(f)
req.on('data', chunk) => {
s.write(chunk);
}
req.on('end', () => {
s.close();
res.end("done")
})
})
server.listen(port)
As it turns out, I had a mistake in my understanding, and therefore made a mistake in my question.
What I thought were the headers, were actually http multipart specification. This is how curl uploads a file when used with this syntax.
What I actually needed was to change the way I test my code with curl to one of the following:
cat /path/to/test/file | curl -T - localhost:8080
# or
curl -T - localhost:8080 < /path/to/test/file
# or
curl -T /path-/to/test/file localhost:8080 < /path/to/test/file
Using the -T (or --upload-file) flag, curl uploads the file (or stdin) without wrapping it in an http form.

Node.js does not pipe contents

I have a simple Web server, which should send a file. I took the code from another answer.
#! /usr/bin/node
const FS = require ('fs');
const HTTP = require ('http');
const server = HTTP.createServer ();
server.on ('request', (request, response) => {
switch (request.url) {
case '/':
switch (request.method) {
case 'GET':
console.log ("GET /");
let stat = FS.statSync ('index.html');
console.log (stat.size);
response.writeHead (200, { 'Content-Type': 'text/html',
'Content-Lenght': stat.size });
let index = FS.createReadStream ('index.html', 'UTF-8');
index.pipe (response);
response.end ();
return;
}
break;
}
response.writeHead (400, {});
response.end ();
});
server.listen (8080);
When I try to send a GET request with curl, I get no content. My server reports, that the index.html file has 324 bytes:
$ ./server.js
GET /
324
But curl does not show the content. There header contains the content length, but the body is missing.
$ curl -v --noproxy \* http://localhost:8080/
[...]
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Content-Lenght: 324
< Date: Sat, 21 Nov 2020 19:24:31 GMT
< Connection: keep-alive
< Transfer-Encoding: chunked
<
* Connection #0 to host localhost left intact
I looks as if the connection got closed before the file has been piped. Is the the error and how can I avoid it?
Remove the response.end ();. You're prematurely closing the response BEFORE the .pipe() gets to do its work (it's asynchronous so it finishes over time and returns before it's done).
In the default configuration, .pipe() will end your response for you when it's done.
You will also notice that the other answer you took this idea from did not have a response.end().

Setting content-type header with restify results in application/octet-stream

I'm trying out restify, and though I'm more comfortable with Express, so far it's pretty awesome. I'm trying to set the content type header in the response like so:
server.get('/xml', function(req, res) {
res.setHeader('content-type', 'application/xml');
// res.header('content-type', 'application/xml'); // tried this too
// res.contentType = "application/xml"; // tried this too
res.send("<root><test>stuff</test></root>");
});
But the response I get back is instead application/octet-stream.
I also tried res.contentType('application/xml') but that actually threw an error ("Object HTTP/1.1 200 OK\ has no method 'contentType'").
What is the correct way to set the content type header to xml on the response?
Update:
When I do console.log(res.contentType); it actually outputs application/xml. Why is it not in the response headers?
Curl snippet:
* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /xml?params=1,2,3 HTTP/1.1
> User-Agent: curl/7.39.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/octet-stream
< Content-Length: 8995
< Date: Mon, 23 Feb 2015 20:20:14 GMT
< Connection: keep-alive
<
<body goes here>
Turns out the reason this was failing is because I was not sending the response using Restify's response handler; it was defaulting to the native Node.js handler.
Where I was doing this:
res.send(js2xmlparser("search", obj));
I should have been doing this:
res.end(js2xmlparser("search", o));
// ^ end, not send!
When I do console.log(res.contentType); it actually outputs application/xml. Why is it not in the response headers?
All you've done there is set a property on the res object. And because this is JavaScript, that works fine and you can read the property value back, but that's not the correct API for either node core or restify, so it is ignored by everything other than your code.
Your res.header("Content-Type", "application/xml"); looks correct to me based on the restify docs you linked to. Therefore my hunch is your tooling may be misleading you. Are you sure you are seeing the raw values in the response (many developer tools will unhelpfully "prettify" or otherwise lie to you) and you are hitting the route you really think you are? Output of curl -v or httpie --headers would be helpful.
It is possible to return application/xml by adding a formatter to the server instance at server creation:
var server = restify.createServer( {
formatters: {
'application/xml' : function( req, res, body, cb ) {
if (body instanceof Error)
return body.stack;
if (Buffer.isBuffer(body))
return cb(null, body.toString('base64'));
return cb(null, body);
}
}
});
Then at some part of the code:
res.setHeader('content-type', 'application/xml');
res.send('<xml>xyz</xml>');
Please, take a look at: http://restify.com/#content-negotiation
You can send the XML response using sendRaw instead of send. The sendRaw method doesn't use any formatter at all (you should preformat your response if you need it). See an example below:
server.get('/xml', function(req, res, next) {
res.setHeader('content-type', 'application/xml');
res.sendRaw('<xml>xyz</xml>');
next();
});

non-chunked multipart/mixed POST?

I'm trying to send multiple binary files to a web service in a single multipart/mixed POST but can't seem to get it to work... target server rejects it. One thing I noticed is Node is trying to do the encoding as chunked, which I don't want:
POST /SomeFiles HTTP/1.1
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=123456789012345
Host: host.server.com
Connection: keep-alive
Transfer-Encoding: chunked
How do I get it to stop being chunked? Docs say that can be disabled by setting the Content-Length in the main request header but I can't set the Content-Length since, among other reasons, I'm loading one file at a time -- but it shouldn't be required either since it's multipart.
I think the rest is OK (excepting it's not chunked, unless the req_post.write() is doing that part), e.g. for the initial part I:
var req_post = https.request({
hostname: 'host.server.com',
path: '/ManyFiles',
method: 'POST',
headers: {
'MIME-Version': '1.0',
'Content-Type': 'multipart/mixed; boundary=' + boundary
}
},...);
and then pseudo-code:
while ( true ) {
// { get next file here }
req_post.write('\r\n--' + boundary + '\r\nContent-Type: ' + filetype + '\r\nContent-Length: ' + filesize + '\r\n\r\n');
req_post.write(chunk);// filesize
if ( end ) {
req_post.write('\r\n--' + boundary + '--\r\n');
req_post.end();
break;
}
}
Any help/pointers is appreciated!
The quick answer is you cannot disable chunked without setting content-length. The chunked encoding was introduced for cases where you do not know the size of the payload when you start transmitting. Originally, content-length was required and the recipient knew it had the full message when it received content-length bytes. Chunked encoding removed that requirement by transmitting mini-payloads each with a content-length, followed by a zero-size to denote completion of the payload. So, if you do not set the content-length, and you do not use the chunked methodology, the recipient will never know when it has the full payload.
To help solve your problem, if you cannot send chunked and do not want to read all the files before sending, take a look at fs.stat. You can use it to get the file size without reading the file.

Handling Accept headers in node.js restify

I am trying to properly handle Accept headers in RESTful API in node.js/restify by using WrongAcceptError as follows.
var restify = require('restify')
; server = restify.createServer()
// Write some content as JSON together with appropriate HTTP headers.
function respond(status,response,contentType,content)
{ var json = JSON.stringify(content)
; response.writeHead(status,
{ 'Content-Type': contentType
, 'Content-Encoding': 'UTF-8'
, 'Content-Length': Buffer.byteLength(json,'utf-8')
})
; response.write(json)
; response.end()
}
server.get('/api',function(request,response,next)
{ var contentType = "application/vnd.me.org.api+json"
; var properContentType = request.accepts(contentType)
; if (properContentType!=contentType)
{ return next(new restify.WrongAcceptError("Only provides "+contentType)) }
respond(200,response,contentType,
{ "uri": "http://me.org/api"
, "users": "/users"
, "teams": "/teams"
})
; return next()
});
server.listen(8080, function(){});
which works fine if the client provides the right Accept header, or no header as seen here:
$ curl -is http://localhost:8080/api
HTTP/1.1 200 OK
Content-Type: application/vnd.me.org.api+json
Content-Encoding: UTF-8
Content-Length: 61
Date: Tue, 02 Apr 2013 10:19:45 GMT
Connection: keep-alive
{"uri":"http://me.org/api","users":"/users","teams":"/teams"}
The problem is that if the client do indeed provide a wrong Accept header, the server will not send the error message:
$ curl -is http://localhost:8080/api -H 'Accept: application/vnd.me.org.users+json'
HTTP/1.1 500 Internal Server Error
Date: Tue, 02 Apr 2013 10:27:23 GMT
Connection: keep-alive
Transfer-Encoding: chunked
because the client is not assumed to understand the error message, which is in JSON, as
seen by this:
$ curl -is http://localhost:8080/api -H 'Accept: application/json'
HTTP/1.1 406 Not Acceptable
Content-Type: application/json
Content-Length: 80
Date: Tue, 02 Apr 2013 10:30:28 GMT
Connection: keep-alive
{"code":"WrongAccept","message":"Only provides application/vnd.me.org.api+json"}
My question is therefore, how do I force restify to send back the right error status code and body, or am I doing things wrong?
The problem is actually that you're returning a JSON object with a content-type (application/vnd.me.org.api+json) that Restify doesn't know (and therefore, creates an error no formatter found).
You need to tell Restify how your responses should be formatted:
server = restify.createServer({
formatters : {
'*/*' : function(req, res, body) { // 'catch-all' formatter
if (body instanceof Error) { // see text
body = JSON.stringify({
code : body.body.code,
message : body.body.message
});
};
return body;
}
}
});
The body instanceof Error is also required, because it has to be converted to JSON before it can be sent back to the client.
The */* construction creates a 'catch-all' formatter, which is used for all mime-types that Restify can't handle itself (that list is application/javascript, application/json, text/plain and application/octet-stream). I can imagine that for certain cases the catch-all formatter could pose issues, but that depends on your exact setup.

Resources