uNetworking / uWebSockets.js

μWebSockets for Node.js back-ends :metal:
Apache License 2.0
8.09k stars 577 forks source link

HTML View #689

Closed Stunext closed 2 years ago

Stunext commented 2 years ago

How to return a simple html view?

e3dio commented 2 years ago
app.get('/',res=>res.end(view({text:'123'})))

const view=data=>`<html><body>${data.text}</body></html>`
sanchezzzhak commented 2 years ago

add header text/html

res.writeHeader('content-type', 'text/html')
res.end('my html code')
Stunext commented 2 years ago

What about a entire HTML file?

e3dio commented 2 years ago

If you are trying to generate a string you can use any template library or I recommend Vanilla JS no library needed, using external file you would do:

// view.js file
module.exports=data=>`
<html>
<body>
   ${data.text}
</body>
</html>
`

// server
const view=require('./view.js')
const htmlString=view(data)

Tell your text editor to use HTML syntax for view.js file so it will highlight as HTML

sanchezzzhak commented 2 years ago

if you need to output static, you need to look at examples with streaming video

I was misunderstood (that's why it's hidden) create file mime.js ```js //https://github.com/sifrr/sifrr/blob/master/packages/server/sifrr-server const mimes = { '3gp': 'video/3gpp', a: 'application/octet-stream', ai: 'application/postscript', aif: 'audio/x-aiff', aiff: 'audio/x-aiff', asc: 'application/pgp-signature', asf: 'video/x-ms-asf', asm: 'text/x-asm', asx: 'video/x-ms-asf', atom: 'application/atom+xml', au: 'audio/basic', avi: 'video/x-msvideo', bat: 'application/x-msdownload', bin: 'application/octet-stream', bmp: 'image/bmp', bz2: 'application/x-bzip2', c: 'text/x-c', cab: 'application/vnd.ms-cab-compressed', cc: 'text/x-c', chm: 'application/vnd.ms-htmlhelp', class: 'application/octet-stream', com: 'application/x-msdownload', conf: 'text/plain', cpp: 'text/x-c', crt: 'application/x-x509-ca-cert', css: 'text/css', csv: 'text/csv', cxx: 'text/x-c', deb: 'application/x-debian-package', der: 'application/x-x509-ca-cert', diff: 'text/x-diff', djv: 'image/vnd.djvu', djvu: 'image/vnd.djvu', dll: 'application/x-msdownload', dmg: 'application/octet-stream', doc: 'application/msword', dot: 'application/msword', dtd: 'application/xml-dtd', dvi: 'application/x-dvi', ear: 'application/java-archive', eml: 'message/rfc822', eps: 'application/postscript', exe: 'application/x-msdownload', f: 'text/x-fortran', f77: 'text/x-fortran', f90: 'text/x-fortran', flv: 'video/x-flv', for: 'text/x-fortran', gem: 'application/octet-stream', gemspec: 'text/x-script.ruby', gif: 'image/gif', gz: 'application/x-gzip', h: 'text/x-c', hh: 'text/x-c', htm: 'text/html', html: 'text/html', ico: 'image/vnd.microsoft.icon', ics: 'text/calendar', ifb: 'text/calendar', iso: 'application/octet-stream', jar: 'application/java-archive', java: 'text/x-java-source', jnlp: 'application/x-java-jnlp-file', jpeg: 'image/jpeg', jpg: 'image/jpeg', js: 'application/javascript', json: 'application/json', log: 'text/plain', m3u: 'audio/x-mpegurl', m4v: 'video/mp4', man: 'text/troff', mathml: 'application/mathml+xml', mbox: 'application/mbox', mdoc: 'text/troff', me: 'text/troff', mid: 'audio/midi', midi: 'audio/midi', mime: 'message/rfc822', mjs: 'application/javascript', mml: 'application/mathml+xml', mng: 'video/x-mng', mov: 'video/quicktime', mp3: 'audio/mpeg', mp4: 'video/mp4', mp4v: 'video/mp4', mpeg: 'video/mpeg', mpg: 'video/mpeg', ms: 'text/troff', msi: 'application/x-msdownload', odp: 'application/vnd.oasis.opendocument.presentation', ods: 'application/vnd.oasis.opendocument.spreadsheet', odt: 'application/vnd.oasis.opendocument.text', ogg: 'application/ogg', p: 'text/x-pascal', pas: 'text/x-pascal', pbm: 'image/x-portable-bitmap', pdf: 'application/pdf', pem: 'application/x-x509-ca-cert', pgm: 'image/x-portable-graymap', pgp: 'application/pgp-encrypted', pkg: 'application/octet-stream', pl: 'text/x-script.perl', pm: 'text/x-script.perl-module', png: 'image/png', pnm: 'image/x-portable-anymap', ppm: 'image/x-portable-pixmap', pps: 'application/vnd.ms-powerpoint', ppt: 'application/vnd.ms-powerpoint', ps: 'application/postscript', psd: 'image/vnd.adobe.photoshop', py: 'text/x-script.python', qt: 'video/quicktime', ra: 'audio/x-pn-realaudio', rake: 'text/x-script.ruby', ram: 'audio/x-pn-realaudio', rar: 'application/x-rar-compressed', rb: 'text/x-script.ruby', rdf: 'application/rdf+xml', roff: 'text/troff', rpm: 'application/x-redhat-package-manager', rss: 'application/rss+xml', rtf: 'application/rtf', ru: 'text/x-script.ruby', s: 'text/x-asm', sgm: 'text/sgml', sgml: 'text/sgml', sh: 'application/x-sh', sig: 'application/pgp-signature', snd: 'audio/basic', so: 'application/octet-stream', svg: 'image/svg+xml', svgz: 'image/svg+xml', swf: 'application/x-shockwave-flash', t: 'text/troff', tar: 'application/x-tar', tbz: 'application/x-bzip-compressed-tar', tcl: 'application/x-tcl', tex: 'application/x-tex', texi: 'application/x-texinfo', texinfo: 'application/x-texinfo', text: 'text/plain', tif: 'image/tiff', tiff: 'image/tiff', torrent: 'application/x-bittorrent', tr: 'text/troff', txt: 'text/plain', vcf: 'text/x-vcard', vcs: 'text/x-vcalendar', vrml: 'model/vrml', war: 'application/java-archive', wav: 'audio/x-wav', wma: 'audio/x-ms-wma', wmv: 'video/x-ms-wmv', wmx: 'video/x-ms-wmx', wrl: 'model/vrml', wsdl: 'application/wsdl+xml', xbm: 'image/x-xbitmap', xhtml: 'application/xhtml+xml', xls: 'application/vnd.ms-excel', xml: 'application/xml', xpm: 'image/x-xpixmap', xsl: 'application/xml', xslt: 'application/xslt+xml', yaml: 'text/yaml', yml: 'text/yaml', zip: 'application/zip', default: 'text/html' }; const getMime = (path) => { const i = path.lastIndexOf('.'); return mimes[path.substr(i + 1).toLowerCase()] || mimes['default']; }; module.exports.getMime = getMime; ``` create file uws-send-file.js ```js const {createBrotliCompress, createGzip, createDeflate} = require('zlib'); const {existsSync, statSync, createReadStream} = require('fs'); const fsPath = require('path'); const {getMime} = require("./mime"); const compressions = { br: createBrotliCompress, gzip: createGzip, deflate: createDeflate }; const defaultOptions = { lastModified: true, headers: {}, compress: true, compressionOptions: { priority: ['gzip', 'br', 'deflate'] } }; const BYTES = 'bytes='; function writeHeaders(res, headers) { for (const n in headers) { res.writeHeader(n, headers[n].toString()); } } /** * * @param {HttpRequest} req * @param {HttpResponse} res * @param {*} options * @return {Promise<*>} * https://github.com/sifrr/sifrr/blob/master/packages/server/sifrr-server/src/server/sendfile.ts */ async function uwsSendFile(req, res, options = {}) { options = Object.assign(defaultOptions, options); let path = options.path; if (path === void 0) { res.writeStatus('404 Not Found'); res.end(); return false; } if (!existsSync(path)) { res.writeStatus('404 Not Found'); res.end(); return false; } let stat = statSync(path); let {mtime, size} = stat; if (!stat.isFile()) { res.writeStatus('404 Not Found'); res.end(); return false; } let headers = options.headers; let ifModifiedSince = req.getHeader('if-modified-since'), range = req.getHeader('range'), acceptEncode = req.getHeader('accept-encoding') mtime.setMilliseconds(0); const mtimeutc = mtime.toUTCString(); if (options.lastModified) { if (ifModifiedSince && new Date(ifModifiedSince) >= mtime) { res.writeStatus('304 Not Modified'); res.end(); return true; } headers['last-modified'] = mtimeutc; } headers['content-type'] = getMime(path); let start = 0, end = size - 1; if (range) { options.compress = false; const parts = range.replace(BYTES, '').split('-'); start = parseInt(parts[0], 10); end = parts[1] ? parseInt(parts[1], 10) : end; headers['accept-ranges'] = 'bytes'; headers['content-range'] = `bytes ${start}-${end}/${size}`; size = end - start + 1; res.writeStatus('206 Partial Content'); } if (end < 0) end = 0; let readStream = createReadStream(path, {start, end}); let compressed = false; if (options.compress) { const l = options.compressionOptions.priority.length; for (let i = 0; i < l; i++) { const type = options.compressionOptions.priority[i]; if (acceptEncode.indexOf(type) > -1) { compressed = type; const compressor = compressions[type](options.compressionOptions); readStream.pipe(compressor); readStream = compressor; headers['content-encoding'] = options.compressionOptions.priority[i]; break; } } } res.onAborted(() => readStream.destroy()); writeHeaders(res, headers); if (compressed) { readStream.on('data', buffer => { res.write(buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)); }); } else { readStream.on('data', buffer => { const chunk = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength), lastOffset = res.getWriteOffset(); // First try const [ok, done] = res.tryEnd(chunk, size); if (done) { readStream.destroy(); } else if (!ok) { // pause because backpressure readStream.pause(); // Save unsent chunk for later res.ab = chunk; res.abOffset = lastOffset; // Register async handlers for drainage res.onWritable(offset => { const [ok, done] = res.tryEnd(res.ab.slice(offset - res.abOffset), size); if (done) { readStream.destroy(); } else if (ok) { readStream.resume(); } return ok; }); } }); } readStream .on('error', e => { res.writeStatus('500 Internal server error'); res.end(); readStream.destroy(); // throw e; }) .on('end', () => { res.end(); }); return true; } module.exports = uwsSendFile; ``` configirate uNetwork server ```js const uwsSendFile = require('./uws-send-file'); const fsPath = require('path'); const publicDir = fsPath.resolve(__dirname + '/public') server.any('/*', (res, req) => { // static files let root = publicDir; let path = req.getUrl(); // code below + example // if (path === '/') { // let controller = new HomeController({res, req}) // return controller.index(); // } /* static output, useful if you have the code on vue or reactjs */ if (path === '/') { return uwsSendFile(req, res, { path: fsPath.join(root, 'index.html'), }); } return uwsSendFile(req, res, { path: fsPath.join(root, path), }); }); ``` let's go ahead and use `ejs` create abstract controller ```js const ejs = require('ejs'); const fs = require('fs'); const {getMime} = require('./mime'); class AbstractController { format = 'html'; statusCode = 200; constructor(opts = {}) { this.req = opts.req; this.res = opts.res; } /** * output server content raw * @param {string} view * @param {string|number|null} statusCode * @param {string|null} format */ renderRaw({view, statusCode, format} = {}) { // set default format if (format === void 0) { format = this.format; } if (format) { this.res.writeHeader('content-type', getMime('.' + format)); } // set default statusCode if (statusCode === void 0) { statusCode = this.statusCode; } if (statusCode) { this.res.writeStatus('' + statusCode); // cast statusCode to string } this.res.end(view); } /** * output server content (prepare ejs template+params) * @param {string} template * @param {*} params * @param {string|number|null} statusCode * @param {string|null} format */ render({template, params, statusCode, format} = {}) { this.renderRaw({ view: ejs.render(template, params), statusCode, format, }); } } module.exports = AbstractController; ``` create frist controller home ```js const AbortController = require('./abstract-controller'); class HomeController extends AbortController { index() { this.render({ template: 'home controller welcome index.html', params: {var1: 1}, // etc format: 'html' }); } } module.exports = HomeController; ``` and now we will add a router for the controller to a cool `uNetwork` server ```js server.get('/home', (res, req) => { let controller = new HomeController({res, req}) controller.index(); }); ``` you can take a ready-made server and not suffer https://github.com/sifrr/sifrr/tree/master/packages/server/sifrr-server for me, this solution is redundant, I only need a part
Wyzix33 commented 2 years ago

if you try to respond with the contents of a html or any other file this is how i did it:

const mime = require('mime');
const fileName = 'path/to/file.html';
try {
   const readStream = toArrayBuffer(fs.readFileSync(fileName));
   res.cork(() => {
    res.writeHeader('Content-Type', mime.getType(fileName););
    res.end(readStream);
   });
  } catch (err) {
   res.cork(() => {
    res.writeStatus('404').end('Not here');
   });

and this is the function

 toArrayBuffer(buffer) {
  return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
 }

You can also consider adding some security Headers, safe resolve the file path, noCache or Cache and also Content-Encoding gzip, deflate or no encoding

e3dio commented 2 years ago

you can take a ready-made server and not suffer

@sanchezzzhak Are you suffering? This is easy stuff, don't confuse him by pasting bunch of unnecessary code, and different library no one should be using

@Wyzix33 You are both assuming static files, he asked for HTML View, usually that means a Template that gets updated with different data on every render, but who knows what he is talking about

e3dio commented 2 years ago

@Stunext Are you asking how to open a basic HTML file? You should probably read https://nodejs.org/api/fs.html