I'm trying to write a simple http web server, that (among other features), can send the client a requested file.
Sending a regular text file/html file works as a charm. The problem is with sending image files.
Here is a part of my code (after parsing the MIME TYPE, and including fs node.js module):
if (MIMEtype == "image") {
console.log('IMAGE');
fs.readFile(path, "binary", function(err,data) {
console.log("Sending to user: ");
console.log('read the file!');
response.body = data;
response.end();
});
} else {
fs.readFile(path, "utf8", function(err,data) {
response.body = data ;
response.end() ;
});
}
Why all I'm getting is a blank page, upon opening http://localhost:<serverPort>/test.jpg?
Here's a complete example on how to send an image with Node.js in the simplest possible way (my example is a gif file, but it can be used with other file/images types):
var http = require('http'),
fs = require('fs'),
util = require('util'),
file_path = __dirname + '/web.gif';
// the file is in the same folder with our app
// create server on port 4000
http.createServer(function(request, response) {
fs.stat(file_path, function(error, stat) {
var rs;
// We specify the content-type and the content-length headers
// important!
response.writeHead(200, {
'Content-Type' : 'image/gif',
'Content-Length' : stat.size
});
rs = fs.createReadStream(file_path);
// pump the file to the response
util.pump(rs, response, function(err) {
if(err) {
throw err;
}
});
});
}).listen(4000);
console.log('Listening on port 4000.');
UPDATE:
util.pump has been deprecated for a while now and you can just use streams to acomplish this:
fs.createReadStream(filePath).pipe(req);
Related
How Can I read Response Header (Content-Disposition)? Please share resolution.
When I check at either Postman or Google Chrome Network tab, I can see 'Content-Disposition' at the response headers section for the HTTP call, but NOT able to read the header parameter at Angular Code.
// Node - Server JS
app.get('/download', function (req, res) {
var file = __dirname + '/db.json';
res.set({
'Content-Type': 'text/plain',
'Content-Disposition': 'attachment; filename=' + req.body.filename
})
res.download(file); // Set disposition and send it.
});
// Angular5 Code
saveFile() {
const headers = new Headers();
headers.append('Accept', 'text/plain');
this.http.get('http://localhost:8090/download', { headers: headers })
.subscribe(
(response => this.saveToFileSystem(response))
);
}
private saveToFileSystem(response) {
const contentDispositionHeader: string = response.headers.get('Content-Disposition'); // <== Getting error here, Not able to read Response Headers
const parts: string[] = contentDispositionHeader.split(';');
const filename = parts[1].split('=')[1];
const blob = new Blob([response._body], { type: 'text/plain' });
saveAs(blob, filename);
}
I have found the solution to this issue. As per Access-Control-Expose-Headers, only default headers would be exposed.
In order to expose 'Content-Disposition', we need to set 'Access-Control-Expose-Headers' header property to either '*' (allow all) or 'Content-Disposition'.
// Node - Server JS
app.get('/download', function (req, res) {
var file = __dirname + '/db.json';
res.set({
'Content-Type': 'text/plain',
'Content-Disposition': 'attachment; filename=' + req.body.filename,
'Access-Control-Expose-Headers': 'Content-Disposition' // <== ** Solution **
})
res.download(file); // Set disposition and send it.
});
It is not the problem with Angular, is the problem with CORS.
If the server does not explicitly allow your code to read the headers, the browser don't allow to read them.
In the server you must add Access-Control-Expose-Headers in the response.
In the response it will be like Access-Control-Expose-Headers:<header_name>,
In asp.net core it can be added while setting up CORS in ConfigureServices method in startup.cs
this solution help me to get the Content-Disposition from response header.
(data)=>{ //the 'data' is response of file data with responseType: ResponseContentType.Blob.
let contentDisposition = data.headers.get('content-disposition');
}
Firstly you need to allow your server to expose these headers. Note that it will show in you browser network tab, regardless if you have these settings. This makes it 'available'.
With C# it would look something like this:
services.AddCors(options => {
options.AddPolicy(AllowSpecificOrigins,
builder => {
builder
.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("Content-Disposition", "downloadFileName");
});
});
When you send your API request to the server ensure that you include the "observe" in you return. See below:
getFile(path: string): Observable<any> {
// Create headers
let headers = new HttpHeaders();
// Create and return request
return this.http.get<Blob>(
`${environment.api_url}${path}`,
{ headers, observe: 'response', responseType: 'blob' as 'json' }
).pipe();
}
Then in your response of your angular on your subscribe you can access your filename like this (the subscribe method is not complete it attaches to a pipe function)
.....
.subscribe((response: HttpResponse<Blob>) => {
const fileName = response.headers.get('content-disposition')
.split(';')[1]
.split('filename')[1]
.split('=')[1]
.trim();
});
Using Meteor HTTP I'm able to get a response from docusign and convert to a base64 buffer.
try {
const response = HTTP.get(`${baseUrl}/envelopes/${envelopeId}/documents/1`, {
headers: {
"Authorization": `bearer ${token}`,
"Content-Type": "application/json",
},
});
const buffer = new Buffer(response.content).toString('base64');
return buffer
} catch(e) {
console.log(e);
throw new Meteor.Error(e.reason);
}
Then on the client I'm using FileSaver.js to saveAs a blob created from an ArrayBuffer via this function
function _base64ToArrayBuffer(base64) {
const binary_string = window.atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array( len );
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
// template helper
'click [data-action="download"]'(e, tmpl){
const doc = this;
return Meteor.call('downloadPDF', doc, (err, pdf)=>{
if(err) {
return notify({
message: err,
timeout: 3000,
})
}
const pdfBuffer = pdf && _base64ToArrayBuffer(pdf);
console.log(pdfBuffer);
return saveAs(new Blob([pdfBuffer], {type: 'application/pdf'}), `docusign_pdf.pdf`);
});
},
The PDF is downloading with the correct size and page length, but all the pages are blank. Should I be encoding the buffer differently? Is there something else I'm missing?
When uploading documents into DocuSign you can either send the raw document bytes as part of a multipart/form-data request or you can send the document as a base64 encoded file in the document node in your request body.
However once the envelope is complete the DocuSign platform converts the doc into a PDF (if it wasn't one already) and provides that raw file. As such, you shouldn't have to base64 decode the doc so I would try removing that part of your code.
I have a standard node.js static file server that I want to use to serve normal html, js, css, and jpg files in the same directory (ie- a typical HTML5 single page app). I would expect that the node server can handle this properly. What I see is different.
The index.html file is served, but then subsequent requests are dropped (ie- they never make it to the server). In my chrome dev tools, I see things like this:
GET http://projectcoho.cloudfoundry.com/css/coho.css http://projectcoho.cloudfoundry.com/:7
GET http://projectcoho.cloudfoundry.com/sencha-touch/sencha-touch-debug.js http://projectcoho.cloudfoundry.com/:8
GET http://projectcoho.cloudfoundry.com/coho-debug.js http://projectcoho.cloudfoundry.com/:8
But, these resources exist on the server and you can reach them if you enter their URL directly. And for these requests, my callback in app.js is never invoked (I can tell this because console.log is never called for these files.
Here is the app.js file:
var path = ".";
var port = process.env.VCAP_APP_PORT || 3000;;
var file = new(static.Server) (path, {
cache: 600
});
mime.define({
'text/css': ['css'],
'text/javascript': ['js'],
'image/jpeg': ['jpg', 'jpeg']
});
http.createServer(function (request, response) {
var uri = url.parse(request.url).pathname;
var filename = libpath.join(path, uri);
console.log("URI: " + request.url + " , filename: " + filename);
libpath.exists(filename, function (exists) {
console.log("Serving " + filename);
if (!exists) {
console.log("Not found");
response.writeHead(404, {
"Content-Type": "text/plain"
});
response.write("404 Not Found\n");
response.end();
return;
}
if (fs.statSync(filename).isDirectory()) {
filename += '/index.html';
}
var type = mime.lookup(filename);
file.serveFile(filename, 200, {'content-type' : type}, request, response);
});
}).listen(port);
What am I missing here?
I am using node v0.6.15
In the end, the answer was that my cache.manifest file was incorrect. The client application was looking for resources in a cache, but the didn't exist. When I corrected the manifest, things started working.
I'm trying to create a static file server in nodejs more as an exercise to understand node than as a perfect server. I'm well aware of projects like Connect and node-static and fully intend to use those libraries for more production-ready code, but I also like to understand the basics of what I'm working with. With that in mind, I've coded up a small server.js:
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs');
var mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "text/javascript",
"css": "text/css"};
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var filename = path.join(process.cwd(), uri);
path.exists(filename, function(exists) {
if(!exists) {
console.log("not exists: " + filename);
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('404 Not Found\n');
res.end();
}
var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
res.writeHead(200, mimeType);
var fileStream = fs.createReadStream(filename);
fileStream.pipe(res);
}); //end path.exists
}).listen(1337);
My question is twofold
Is this the "right" way to go about creating and streaming basic html etc in node or is there a better/more elegant/more robust method ?
Is the .pipe() in node basically just doing the following?
.
var fileStream = fs.createReadStream(filename);
fileStream.on('data', function (data) {
res.write(data);
});
fileStream.on('end', function() {
res.end();
});
Thanks everyone!
Less is more
Just go command prompt first on your project and use
$ npm install express
Then write your app.js code like so:
var express = require('express'),
app = express(),
port = process.env.PORT || 4000;
app.use(express.static(__dirname + '/public'));
app.listen(port);
You would then create a "public" folder where you place your files. I tried it the harder way first but you have to worry about mime types which is just having to map stuff which is time consuming and then worry about response types, etc. etc. etc.... no thank you.
Your basic server looks good, except:
There is a return statement missing.
res.write('404 Not Found\n');
res.end();
return; // <- Don't forget to return here !!
And:
res.writeHead(200, mimeType);
should be:
res.writeHead(200, {'Content-Type':mimeType});
Yes pipe() does basically that, it also pauses/resumes the source stream (in case the receiver is slower).
Here is the source code of the pipe() function: https://github.com/joyent/node/blob/master/lib/stream.js
I like understanding what's going on under the hood as well.
I noticed a few things in your code that you probably want to clean up:
It crashes when filename points to a directory, because exists is true and it tries to read a file stream. I used fs.lstatSync to determine directory existence.
It isn't using the HTTP response codes correctly (200, 404, etc)
While MimeType is being determined (from the file extension), it isn't being set correctly in res.writeHead (as stewe pointed out)
To handle special characters, you probably want to unescape the uri
It blindly follows symlinks (could be a security concern)
Given this, some of the apache options (FollowSymLinks, ShowIndexes, etc) start to make more sense. I've update the code for your simple file server as follows:
var http = require('http'),
url = require('url'),
path = require('path'),
fs = require('fs');
var mimeTypes = {
"html": "text/html",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"png": "image/png",
"js": "text/javascript",
"css": "text/css"};
http.createServer(function(req, res) {
var uri = url.parse(req.url).pathname;
var filename = path.join(process.cwd(), unescape(uri));
var stats;
try {
stats = fs.lstatSync(filename); // throws if path doesn't exist
} catch (e) {
res.writeHead(404, {'Content-Type': 'text/plain'});
res.write('404 Not Found\n');
res.end();
return;
}
if (stats.isFile()) {
// path exists, is a file
var mimeType = mimeTypes[path.extname(filename).split(".").reverse()[0]];
res.writeHead(200, {'Content-Type': mimeType} );
var fileStream = fs.createReadStream(filename);
fileStream.pipe(res);
} else if (stats.isDirectory()) {
// path exists, is a directory
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Index of '+uri+'\n');
res.write('TODO, show index?\n');
res.end();
} else {
// Symbolic link, other?
// TODO: follow symlinks? security?
res.writeHead(500, {'Content-Type': 'text/plain'});
res.write('500 Internal server error\n');
res.end();
}
}).listen(1337);
var http = require('http')
var fs = require('fs')
var server = http.createServer(function (req, res) {
res.writeHead(200, { 'content-type': 'text/plain' })
fs.createReadStream(process.argv[3]).pipe(res)
})
server.listen(Number(process.argv[2]))
How about this pattern, which avoids checking separately that the file exists
var fileStream = fs.createReadStream(filename);
fileStream.on('error', function (error) {
response.writeHead(404, { "Content-Type": "text/plain"});
response.end("file not found");
});
fileStream.on('open', function() {
var mimeType = mimeTypes[path.extname(filename).split(".")[1]];
response.writeHead(200, {'Content-Type': mimeType});
});
fileStream.on('end', function() {
console.log('sent file ' + filename);
});
fileStream.pipe(response);
I made a httpServer function with extra features for general usage based on #Jeff Ward answer
custtom dir
index.html returns if req === dir
Usage:
httpServer(dir).listen(port);
https://github.com/kenokabe/ConciseStaticHttpServer
Thanks.
the st module makes serving static files easy. Here is an extract of README.md:
var mount = st({ path: __dirname + '/static', url: '/static' })
http.createServer(function(req, res) {
var stHandled = mount(req, res);
if (stHandled)
return
else
res.end('this is not a static file')
}).listen(1338)
#JasonSebring answer pointed me in the right direction, however his code is outdated. Here is how you do it with the newest connect version.
var connect = require('connect'),
serveStatic = require('serve-static'),
serveIndex = require('serve-index');
var app = connect()
.use(serveStatic('public'))
.use(serveIndex('public', {'icons': true, 'view': 'details'}))
.listen(3000);
In connect GitHub Repository there are other middlewares you can use.
I'm trying to get a node.js server to send css files. I'm modifying this server here:
http://github.com/LearnBoost/Socket.IO-node/blob/master/test/server.js
What's wrong with what I'm doing:
server = http.createServer(function(req, res){
// your normal server code
var path = url.parse(req.url).pathname;
switch (path){
case '/':
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('<h1>Welcome. Try the chat example.</h1>');
res.end();
break;
default:
if (/\.(js|html|swf)$/.test(path)){
try {
var swf = path.substr(-4) === '.swf';
res.writeHead(200, {'Content-Type': swf ? 'application/x-shockwave-flash' : ('text/' + (path.substr(-3) === '.js' ? 'javascript' : 'html'))});
res.write(fs.readFileSync(__dirname + path, swf ? 'binary' : 'utf8'), swf ? 'binary' : 'utf8');
res.end();
} catch(e){
send404(res);
}
break;
}
else if (/\.(css)$/.test(path)){
res.writeHead(200, {'Content-Type': 'text/css'});
res.end();
break;
}
send404(res);
break;
}
});
Thanks.
You're only writing a response header for css requests, for one.
I'd imagine if you call curl -I http://your-server/some-file.css, you'd get back a 200 status with a Content-Length of 0. You could just get away with:
res.write(fs.readFileSync(__dirname + path, 'utf8'));
But a.) Don't Repeat Yourself, and b.) the 'sync' in both of those methods means synchronous. It probably isn't for this version, but in Node in general, you should be just calling readFile and passing a callback to finish the request later on. The API isn't great for directly linking, but the File System section should help, look for 'fs.readFile'.
You forgot to send the file.
...
else if (/\.(css)$/.test(path)){
res.writeHead(200, {'Content-Type': 'text/css'});
res.write(fs.readFileSync(__dirname + path, 'utf8')); // <--- add this line
res.end();
break;
}