Code Monkey home page Code Monkey logo

inert's Introduction

@hapi/inert

Static file and directory handlers for hapi.js.

inert is part of the hapi ecosystem and was designed to work seamlessly with the hapi web framework and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out hapi – they work even better together.

Visit the hapi.dev Developer Portal for tutorials, documentation, and support

Useful resources

inert's People

Contributors

arb avatar behnoodk avatar cjihrig avatar devinivy avatar escalant3 avatar hueniverse avatar jarrodyellets avatar kalinkrustev avatar kanongil avatar lloydbenson avatar marsup avatar mattboutet avatar mzimokha avatar nargonath avatar nwhitmont avatar paypactroy avatar pierreinglebert avatar prashaantt avatar shruthi-alva avatar simon-p-r avatar yortus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

inert's Issues

Setting custom headers for the file/directory handler

I need to change the Cache-control and add other custom header to all static assets, served by the directory handler.

The only solution, I found, was to set the headers using the onPreResponse hook, but this requires checking the if file path matches the directory router.

Is there a better way to add/modify the headers for these handlers ?

Is it possible to use Inert with Handlebars (Vision)?

I am extremely new to hapi and I am hoping to create a simple prototyping framework using it. I would like to use Handlebars for templating (which means using Vision), is it possible to also use inert to serve the files? Is it possible for the routes be determined from the handlebars view files?

Hapijs directory route not being scrapped by facebook behind cloudfront

I initially filed this here: hapijs/hapi#3132 Was directed here.

I wonder if anyone has hit this?

I have this https://donate.mozilla.org/en-US/

Which is a hapi server. In this case it's serving a static html file: https://github.com/mozilla/donate.mozilla.org/blob/master/server.js#L352-L358

 server.route([{
      method: 'GET',
      path: '/{params*}',
      handler: {
        directory: {
          path: Path.join(__dirname, 'public')
        }
      }
}

Seems to work fine as a file server. However, when it interacts with cloudfront and facebooks scrapper, something breaks. Not fully understanding what's happening, but what I can piece together is:

The hapi server sends the file contents as Transfer-Encoding: chunked

Cloudfront then has "If the viewer makes a Range GET request and the origin returns Transfer-Encoding: chunked, CloudFront returns the entire object to the viewer instead of the requested range." from http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/RangeGETs.html

Facebook's scrapper then chokes on the size of the range not being expected.

You can test that here: https://developers.facebook.com/tools/debug/og/object/

Paste in: http://donate.mozilla.org/en-US/thunderbird/

Then click "fetch new scrape information"

Facebook has provided me with a curl command that simulates what their scrapper does:

curl -G -v --compressed -H "Range: bytes=0-500000" -H "Connection: close" -A "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)" "https://donate.mozilla.org/en-US/thunderbird/share/"

It responds with curl: (18) transfer closed with 4317 bytes remaining to read

It also doesn't respond with Transfer-Encoding: chunked

If I curl directly to the server without cloudfront:

curl -G -vv --compressed -H "Range: bytes=0-500000" -H "Connection: close" -A "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)" "https://donate-mozilla-org-us-prod.herokuapp.com/en-US/"

I get back Transfer-Encoding: chunked which I then think cloudfront returns another thing to facebook which facebook doesn't expect.

Thoughts? Can I just turn off Transfer-Encoding: chunked and how would I do that with the static directory server?

Replying images to client

Hi there,

I'm serving a static image file to client and I'm facing troubles to decode it.

Is there any way to retrieve base64 encoded string from it?

Thanks

lookupCompressed not working as I expected

I have a bunch of javascript files I'm serving with Inert. To reduce their file size I gzipped them and erased the uncompressed versions, but when trying to serve them I get a 404 error. When the uncompressed files are present there are no issues.

I expect Inert to serve the compressed version of the files without needing the uncompressed to be right there next to it. Is there something wrong with these assumptions?

Thanks.

New ETag generation method

Currently ETags are calculated based on the SHA1 hash of the file. This requires a file to be completely read before its tag can be calculated and put into the header of a request. To circumvent this, inert currently omits the ETag header until a complete file has been served to a client, caching the value for future requests.

The file hashing causes undue complexity for limited gains, and I would like to replace this with a value computed from: modification time + file size, similar to nginx & express.js.

As far as I can tell, this updated method should mainly affect cases where the same file is stored on multiple servers, served by different hapi instances behind a load balancer. Currently they are always served with the same ETag, but will require synchronization of the modification dates to achieve the same result with the new method.

I'm considering to completely replace the generation method but am also considering adding it as a new etagMethod option, to allow consistent tag values across servers for git deployed assets, etc.

Any input on this would be much appreciated.

Content-Type header based on filename option

Hello,

I would like inert set Content-Type header based on filename option instead of path filename.

for usage example

reply.file(doc.fullPath, {
    filename: doc.name,
    mimeFromFilename: true
});

for current my workaround in lib/file.js

        if (!response.headers['content-type']) {
            var mimePath;
            if (response.source.settings.mimeFromFilename) {
                mimePath = response.source.settings.filename;
            } else {
                mimePath = path;
            }
            response.type(response.request.server.mime.path(mimePath).type || 'application/octet-stream');
        }

Thanks

Inert unable to serve my .css file

Hi,

I'm unable to serve a css file using Inert and Vision.

I have an issue to serve my app.css file in the default.html view using hapijs/Inert/Vision. I use Cloud9 for hosting but it shouldn't be a problem.

Here is my app structure

app/
   client/
      config/
         route.js
      css/
         app.css
      view/
         layout/
            default.html
         index.html
server.js

My server.js

'use strict';

const Hapi      = require('hapi');
const Vision    = require('vision');
const Inert     = require('inert');
const Path      = require('path');
const Routes    = require('./client/config/route')

const server = new Hapi.Server();

server.connection({
    host: process.env.IP || '0.0.0.0',
    port: process.env.PORT || 3000
});

server.register([Vision, Inert], (err) => {
    if (err) {
        throw err;
    }

    server.views({
        engines: {
            html: require('handlebars')
        },
        relativeTo: Path.join(__dirname, 'client'),
        path: 'views',
        layoutPath: 'views/layouts',
        layout: 'default', // true
        partialsPath: 'views/partials'
    });

    server.route(Routes);
});

server.start(() => {
    console.log('Server started at: ' + server.info.uri);
});

My route.js

const Path  = require('path');

const routes = [
    {
        method: 'GET',
        path: '/{param*}',
        handler: {
            directory: {
                path: Path.join(__dirname, 'client'),
                listing: false,
                index: false
            }
        }
    },
    {
        method: 'GET',
        path: '/',
        handler: (req, rep) => {

            let data = { };                
            rep.view('index', data);
        }
    }
];

module.exports = routes;

My default.html using handlebars

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8" />
        <title>Title</title>
        <link rel="stylesheet" href="../../css/app.css" />
    </head>
    <body>
        {{{content}}}
    </body>
</html>

My index.html

<p id="paragraph">Hello World<p>

The issue is that my app.css doesn't load. I have followed this advice and this one but it still doesn't work and I don't know what to check next. Is something wrong with Inert plugin or am I doing something wrong?

Potentially incorrect file responses when file is changed

If a file is changed during file response processing, invalid responses are likely to be generated, including:

  • Content-Length, Last-Modified, and ETag header from old file, with new file data.
  • Trying to serve a non-existing file, responding with an ENOENT error.
  • Trying to serve a directory responding with an EISDIR error.

These issues should be able to be fixed by using a file descriptor and stat'ing / reading from it.

How to detect if path does not exist 404

I am trying to setup a cache proxy thing, if the file is on disk I want to just serve it up, otherwise I'll proxy the request, pipe the response to the client, and write the file to disk. I have the code for the proxy working fine, I would just like to hook into reply.file() if it is going to return a 404 error. Is this possible, or beyond the scope of this module?

lookupCompressed flag does not work on directory

I have the following directory setup for public folders

    {
        method: "GET",
        path: "/assets/{path*}",
        config: {
            description: 'Serve static content from public folder with director listing',
            handler: {
                directory: {
                    path: "public",
                    lookupCompressed: true
                }
            },
            tags: ['assets']
        }
    },

in the folders I have the following files:

client-2.0.0.min.css - size: (97,959)
client-2.0.0.min.css.gz - size: (36,210)
vendor-3.0.0.min.css - size: (148,214)
vendor-3.0.0.min.css.gz - size: (24,153)

When I request the asset it should serve back with the gzipped file, the response header should contain: content-encoding': 'gzip'
but nothing. I tried to trace it in the library and I checked the file.js properly looks for the gz file and sets the header info but it all get lost in the directory callback and revert back to serve the min.js file.

I tried to delete the min.js files to "force" to serve back the gz files but then just fails with file not found.

Allow directory handler to serve files containing '..'

Currently, any path containing the characters .. will result in a Boom.forbidden() response.

While this prevents serving files from unauthorized directories, it seems rather excessive and counter-intuitive. For instance this prevents misc..txt from being served.

Support brotli compression

Hi.

I'm working on adding brotli support for Hapi as requested in hapijs/hapi#3037

I made the required changes on my Hapi, Inert, and Subtext forks and added the required tests. All tests are passing in Hapi and Subtext but I still have one failing test in Inert.

You can see all the changes in my forks but the failing test code is the following:

it('returns a subset of a file using br precompressed file', (done) => {

    const server = provisionServer();
    server.route({ method: 'GET', path: '/file', handler: { file: { path: Path.join(__dirname, 'file/image.png'), lookupCompressed: true } } });
    server.inject({ url: '/file', headers: { 'range': 'bytes=10-18', 'accept-encoding': 'br' } }, (res) => {

        expect(res.statusCode).to.equal(206);
        expect(res.headers['content-encoding']).to.equal('br');
        expect(res.headers['content-length']).to.equal(9);
        expect(res.headers['content-range']).to.equal('bytes 10-18/41978');
        expect(res.headers['accept-ranges']).to.equal('bytes');
        expect(res.payload).to.equal('image.png');
        done();
    });
});

It is the same test that is used to test gzip compression but with the small changes required to test brotli compression.

It fails in the following line:

expect(res.payload).to.equal('image.png');

The res.payload returns "\u0019�Q�".

After a lot of work, I'm still not able to find what the issue is. I was wondering if someone could point me in the right direction.

I also noticed that there is a open issue that could be related (although I guess the test should pass independently of the proposed feature). I'm referring to hapijs/hapi#3133

Thanks,
Nicolas

Adding custom headers with the directory handler

Hello guys. I have a question: is there any way to use the directory handler and add some custom headers? I'm trying to combine the convenience of the directory handler guarding from malevolent input and still being able to add some fixed headers to the files. Something like:

server.route({
  method: 'GET',
  path: '/static/{data*}',
  handler: {
    directory: {
      index: false,
      path: 'static/data/',
      customHeaders: [
        {name: 'X-Some-Custom-Header', value: 'someCustomValue'},
        {name: 'X-Another-Custom-Header', value: 'anotherCustomValue'}
      ]
    }
  }
});

Improvement: regex for path rewrite

I have a HTML5 webapp (in AngularJS) that uses paths in the app for navigation.

For this I need a mapping between each folder to my index.html file
(so /home, /admin/users, /basket) should all return the same file when refreshing the browser (the clientside path is updated with $location)

Of course, all other files that are in subfolders need to load correctly (for instance /assets/script.js)

For this I modified the handler function of directory.js (line 64) with an additional check:

    var handler = function (request, reply) {

                // path doesn't contain a '.', return index file
                if(request.path.match('^[^\\.]*$')) {
                    reply.file('./index.html');
                } else { ... rest of the current function

It would be nice if this would become a property of the settings object (like the path function) but I'm not quite sure how to accomplish this.

Since rewriting is a common functionality of other webservers, I think there should be a place for this. Another option would be to have regex functionality in hapi's router.

ES6 style changes and node v4

Apply the latest ES6 hapi style:

  • use strict
  • const and let where appropriate (linter will tell you where)
  • arrow functions where appropriate (linter will tell you where)
  • relevant updates to tests + docs
  • updating deps and engine in package.json
  • avoid self and += / -= (not optimized yet for let const)
  • change .travis to test 4.0, 4, and 5

Handle Range request

Currently, Range responses are handled by hapi slicing the response. However for file responses it would make more sense to only read the requested range from the file.

Add custom headers to file and directory handler responses

Would be fantastic if - for various reasons - file and directory handlers could also set headers. This is especially useful for setting simple surrogate keys for CDNs (eg, fastly) but useful in other areas too.

I would imagine it would just be a simple key:value object passed in the options.

Thoughts?

4.0.0 Release Notes

Summary

The 4.0.0 release is a breaking change, mainly due to updated code syntax that relies on ES6 features that aren't supported in node v0.10.

This release also introduces a new security feature that is intended to prevent accidentally exposing private files on your server. The previous releases allowed any file to be served, which when combined with a non-sanitized path to reply.file(), can potentially return any file on the server.

  • Upgrade time: low to medium - no code changes required for many cases.
  • Complexity: medium - non-standard path usage can have new runtime behavior.
  • Risk: moderate - some files might not be served as expected.
  • Dependencies: none

Breaking Changes

  • Node v0.10 is no longer supported.
  • Directly serving files outside the files.relativeTo path returns a 403 response instead of the file contents.

New Features

  • New confine security option to control file system access restrictions.

Updated Dependencies

  • ammo from 1.x to 2.x
  • boom from 2.x to 3.x
  • hoek from 2.x to 4.x
  • items from 1.x to 2.x
  • joi from 6.7.x to 8.x
  • lru-cache from 2.7.x to 4.0.x

Migration

Files that would previously be returned successfully can now respond with a Boom.forbidden / 403 code instead of served outside the files.relativeTo directory. To fix this for the file handler and reply.file() method:

  • Ensure that the path being served is within the files.relativeTo directory.
  • Alternatively, specify the confine option with a suitable base path.
  • As a last resort, you can use the confine: false option to disable this security feature.

For the directory handler, no changes are necessary unless you rely on serving files outside the directory pointed to by the path option. In this case you need to reconsider the security implications of what you are doing.

EMFILE 'Failed to open file' error when hapi serves static files. hapi 8.4.0

reopen from hapijs/hapi#2507

Suddenly after weeks working on an application without any problems this error (below) - trying to serve my front-end main.js - comes up irregular but continuous. After restarting the server it's gone for a while, but comes up again.

I saw an old issue so maybe this is related to hapijs/hapi#1387 ?

But i don't fully understand. Is this error related to hapi, node or maybe my build process (gulp) ?

Thank you very much for your help. Or any hint how i can further investigate this error.

hapi: 8.4.0
node: 0.10.33
gulp: 3.8.11
OSX: 10.9.5
Chrome: 42.0.2311.90

Debug: handler, error
{
"msec":0.32216699980199337,
"error":"Failed to open file: EMFILE, open 'static/main.js'",
"data": {
    "errno":20,
    "code":"EMFILE",
    "path":"static/main.js",
    "isBoom":true,"data":null,
    "output": {
        "statusCode":500,
        "payload":{
            "statusCode":500,
            "error":"Internal Server Error",
            "message":"An internal server error occurred"},
            "headers":{}
}}}

150418/083817.200, [error], message: Failed to open file: EMFILE, open 'static/main.js' 
stack: Error: Failed to open file: EMFILE, open 'static/main.js'

this is my static route inside a plugin:

server.route({
        path: '/static/{file*}',
        method: 'GET',
        config: {
            auth: false
        },
        handler: {
            directory: {
                path: './static',
                listing: false,
                index: false
            }
        }
    });

my impression is that the errors are present after rebuilding static files with gulp and then reloading the server.

thanks.

Move hapi security tests here

    it('blocks path traversal to files outside of hosted directory is not allowed with null byte injection', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/%00/../security.js', function (res) {

            expect(res.statusCode).to.equal(403);
            done();
        });
    });

    it('blocks path traversal to files outside of hosted directory is not allowed', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/../security.js', function (res) {

            expect(res.statusCode).to.equal(403);
            done();
        });
    });

    it('blocks path traversal to files outside of hosted directory is not allowed with encoded slash', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/..%2Fsecurity.js', function (res) {

            expect(res.statusCode).to.equal(403);
            done();
        });
    });

    it('blocks path traversal to files outside of hosted directory is not allowed with double encoded slash', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/..%252Fsecurity.js', function (res) {

            expect(res.statusCode).to.equal(403);
            done();
        });
    });

    it('blocks path traversal to files outside of hosted directory is not allowed with unicode encoded slash', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/..\u2216security.js', function (res) {

            expect(res.statusCode).to.equal(403);
            done();
        });
    });

    it('blocks null byte injection when serving a file', function (done) {

        var server = new Hapi.Server();
        server.connection();
        server.route({ method: 'GET', path: '/{path*}', handler: { directory: { path: './directory' } } });

        server.inject('/index%00.html', function (res) {

            expect(res.statusCode).to.equal(404);
            done();
        });
    });

Customizable directory listings

The HTML generated for directory listings is currently hard-coded and very basic. It would be nice to make this configurable, allowing translations and other custom representations.

This can be enabled by allowing the listing option to accept a custom render function. Probably something like function (context, callback), with context an object containing:

  • resource
  • parentPath
  • files - Array of objects representing visible files with:
    • name
    • path

The callback should probably expect a function (err, rendered) reply.

With this in place, you could render a handlebars template like this:

<html>
  <head><title>{{resource}}</title></head>
  <body>
    <h1>Directory: {{resource}}</h1>
    <ul>
      {{#if parentPath}}<li><a href="{{parentPath}}"></li>{{/if}}
      {{#each files}}<li><a href="{{this.path}}">{{this.name}}</a></li>{{/each}}
    </ul>
  </body>
</html>
var renderTemplate = Handlebars.compile();
var listingRenderFn = function (context, callback) {

    return callback(null, renderTemplate(context));
}

server.route({
    method: 'GET',
    path: '/files/{path*}',
    handler: { directory: { path: './public', listing: listingRenderFn } }
});

I will have a look at implementing this, but would appreciate feedback on the approach.

/cc @jagoda & @Trippnology who inspired this in hapijs/hapi#2656.

ETag generation can miss data

When ETags are generated they is generated based on the output stream data. If this is closed for any reason, the tag is still generated based on the partial data. Further, if the generating request contains a range request to return partial data, it is generated based on that range.

Attachment Content Length

For attachments, the content length is not sent which makes downloads unpredictable.

Adding

    response._header('content-length', Fs.statSync(path)["size"]);

should fix it?

auth: false

Is it possible to use auth (for example "auth: false") for "directory" handler in principle ?

Handling 404 error

Hi,
I'm playing with the directory handler, which greatly simplified my code, but I'm missing the possibility to redirect to a custom 404 page when the file does not exist.

Is there a way to do that, like with a reverse proxy or a function in the parameters of the handlers?

Thank you,

Jb

Error: Invalid plugin options {}

Hi all!
I am approaching for the first time to Hapi, and I wrote my first server code following the tutorial without any kind of problems.

Now I'm trying to add inert for load a static html file, but I get this error.

delvedor$ npm run server

> [email protected] server /Users/delvedor/Development/app
> node server.js

/Users/delvedor/Development/app/node_modules/hapi/node_modules/hoek/lib/index.js:731
    throw new Error(msgs.join(' ') || 'Unknown error');
    ^

Error: Invalid plugin options {}

[1] "once" is not allowed
    at Object.exports.assert (/Users/delvedor/Development/app/node_modules/hapi/node_modules/hoek/lib/index.js:731:11)
    at Object.exports.assert (/Users/delvedor/Development/app/node_modules/hapi/lib/schema.js:15:10)
    at internals.Plugin.register (/Users/delvedor/Development/app/node_modules/hapi/lib/plugin.js:221:25)
    at Object.<anonymous> (/Users/delvedor/Development/app/server.js:20:8)
    at Module._compile (module.js:434:26)
    at Object.Module._extensions..js (module.js:452:10)
    at Module.load (module.js:355:32)
    at Function.Module._load (module.js:310:12)
    at Function.Module.runMain (module.js:475:10)
    at startup (node.js:118:18)

Following my server code:

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server();

server.connection({
  host: 'localhost',
  port: 3000
});

server.register([require('inert'), {
  register: require('good'),
  options: {
    reporters: [{
      reporter: require('good-console'),
      events: {
        response: '*',
        log: '*'
      }
    }]
  }
}], function(err) {
  if (err)
    console.error('Failed to load plugin:', err);

  server.start(() => {
    server.log('info', `Server running at: ${server.info.uri}`);
  });
});

server.route({
  method: 'GET',
  path: '/{param*}',
  handler: {
    directory: {
      path: 'public',
      index: ['index.html']
    }
  }
});

Node: v4.1.2
npm: v2.14.4
hapi: v10.3.0
inert: v3.1.0

What am I doing wrong?
(I get the same error following the docs inside the inert readme)

Move plugin documentation from hapi

  • file - generates a static file endpoint for serving a single file. file can be set to:
    • a relative or absolute file path string (relative paths are resolved based on the
      route files configuration).
    • a function with the signature function(request) which returns the relative or absolute
      file path.
    • an object with the following options:
      • path - a path string or function as described above.
      • filename - an optional filename to specify if sending a 'Content-Disposition'
        header, defaults to the basename of path
      • mode - specifies whether to include the 'Content-Disposition' header with the
        response. Available values:
        • false - header is not included. This is the default value.
        • 'attachment'
        • 'inline'
      • lookupCompressed - if true, looks for the same filename with the '.gz' suffix
        for a pre-compressed version of the file to serve if the request supports content
        encoding. Defaults to false.
  • directory - generates a directory endpoint for serving static content from a directory.
    Routes using the directory handler must include a path parameter at the end of the path
    string (e.g. /path/to/somewhere/{param} where the parameter name does not matter). The
    path parameter can use any of the parameter options (e.g. {param} for one level files
    only, {param?} for one level files or the directory root, {param*} for any level, or
    {param*3} for a specific level). If additional path parameters are present, they are
    ignored for the purpose of selecting the file system resource. The directory handler is an
    object with the following options:
    • path - (required) the directory root path (relative paths are resolved based on the
      route files configuration). Value can be:
      • a single path string used as the prefix for any resources requested by appending the
        request path parameter to the provided string.
      • an array of path strings. Each path will be attempted in order until a match is
        found (by following the same process as the single path string).
      • a function with the signature function(request) which returns the path string or
        an array of path strings. If the function returns an error, the error is passed back
        to the client in the response.
    • index - optional boolean|string|string[], determines if an index file will be served if found in the
      folder when requesting a directory. The given string or strings specify the name(s) of the index file to look for. If true, looks for 'index.html'. Any falsy value disables index file lookup. Defaults to true.
    • listing - optional boolean, determines if directory listing is generated when a
      directory is requested without an index document.
      Defaults to false.
    • showHidden - optional boolean, determines if hidden files will be shown and served.
      Defaults to false.
    • redirectToSlash - optional boolean, determines if requests for a directory without a
      trailing slash are redirected to the same path with the missing slash. Useful for
      ensuring relative links inside the response are resolved correctly. Disabled when the
      server config router.stripTrailingSlash is true.Defaults to false.
    • lookupCompressed - optional boolean, instructs the file processor to look for the same
      filename with the '.gz' suffix for a pre-compressed version of the file to serve if the
      request supports content encoding. Defaults to false.
    • defaultExtension - optional string, appended to file requests if the requested file is
      not found. Defaults to no extension.

Unable to download file as an attachment

Hapi version: 16.0.0
Inert version: 4.0.1

const fileName = 'whatever.txt';
const filePath = `${os.tmpdir()}/${fileName}`;

fs.writeFile(filePath, 'some_random_text', function(err) {
     if (err) return reply(Boom.internal(err));
     reply.file(filePath, {confine: false, filename: fileName, mode: 'attachment'});
});

The file gets rendered instead of being downloaded. Am I missing something here?
Thanks. #

Make an any-depth directory parameter also optional

I am struggling to create a route that does what I need due to this constraint set by inert:

Routes using the directory handler must include a path parameter at the end of the path string

Here is my route:

const path = require('path');
const findUp = require('find-up');

module.exports = {
    method  : 'GET',
    path    : '/{appName}/s;id={siteId}/{file*}',
    handler : {
        directory : {
            path : (request) => {
                const { appName } = request.params;
                return findUp.sync(path.join(appName, 'build'));
            },
            redirectToSlash : true,
            listing         : true
        }
    }
};

The above route works perfectly in almost every way. It lets me do a GET to http://localhost/my-app/s;id=xxx/js/my-app.js for example. And it correctly serves the file at /Users/sholladay/Code/personal/my-app/build/js/my-app.js. Awesome.

However, I need the user to be able to GET http://localhost/my-app/s;id=xxx/ and see a directory listing on that build directory. In other words, I need the last parameter to be optional. But using {file?} does not work because then you can't request anything inside of the js directory.

At first I thought "okay, I'll just create another route with a different path but identical handler", along the lines of:

module.exports = {
    method  : 'GET',
    path    : '/{appName}/s;id={siteId*}',
    ...
};

But this doesn't work because {siteId} isn't completely standalone as the last path component. And it wouldn't really do what I want anyway, since siteId would then contain junk.

I imagine I could hack around this by moving the s;id= prefix into route validation instead and using substring() to remove it. But this is getting ugly quick. What I want seems pretty simple: map the path to a specific base. Kind of how h2o2 behaves, but for the local filesystem.

Any thoughts / suggestions? I am posting this here rather than on the discuss repo as this seems like a feature request.

Error: Unknown handler: directory

Hi,
I am using inert to serve static content, but unfortunately it is not working properly, instead i am getting the following error :

Error: Unknown handler: directory at Object.exports.assert (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hoek/lib/index.js:736:11) at Object.exports.defaults (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/handler.js:111:14) at new module.exports.internals.Route (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/route.js:60:37) at internals.Connection._addRoute (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/connection.js:411:19) at internals.Connection._route (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/connection.js:403:18) at internals.Plugin._apply (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/plugin.js:588:14) at internals.Plugin.route (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/node_modules/hapi/lib/plugin.js:558:10) at Object.<anonymous> (/Users/smahi/Sites/HapiJS_Projects/rassif-imar.com/server.js:15:8) at Module._compile (module.js:570:32) at Object.Module._extensions..js (module.js:579:10)

even though i properly setup the plugin and the route.

{
        method: 'GET',
        path: '/{param*}',
        handler: {
            directory: {
                path: './public',
                redirectToSlash: true,
                index: true
            }
        }
    }
{
        register: require('inert')
 }

Did i miss something important ?

"The route path must end with a parameter"

The following route fails hard at this line:

server.route({
    path: '/favicon.ico',
    method: 'GET',
    handler: {
        directory: {
            path: 'public'
        }
    }
});

The tutorial says it's allowed (and preferred over /favicon.{ext} — which of course does work).

I don't know enough yet but I think all routing rules ought to be deferred to hapi core.

Cannot read property 'lookupCompressed' of undefined

Using Hapi v11.1.4, I'm getting this error here /lib/file.js:110:34 when I'm using reply.file. This is what my sample code looks like:

server.route({
method: 'GET',
path: '/users/{userName}/image',
handler: function (request, reply) {
reply.file('/full/path/to/image.png');
}
});

I also tried sending in an options object and setting all of the values as well.

Can't access to the static route from the server side

Hello.

I just tried to use Hapi + inert to have an easy access to my public folder from the server side like with Express but I have some troubles and I didn't found any solution on the web.
So I come here hoping to find some help from the community.

My public folder is outside the server folder.

- public
-- documents
--- bills
- server
-- api
-- config
-- node_modules
-- utils

With Express when I write
app.use(express.static('public'));
I can access to the public folder from my controller with fs-path module just by writing

fsPath.find('public', function(err, list){
    console.log(list); // list the content of my public folder outside the server folder
});

With Hapi combined with inert it doesn't work and I don't know why.
I can access to this folder from my browser with the url "localhost:3000/public" [I can see the list of subfolders it's ok for the test] but not from the server.

Part of my server.js file

// ...
server.register(require('inert'), function(err){
        if (err) {
            logger.warn(err);
        }
        server.route({
            method: 'GET',
            path: '/public/{param*}',
            handler: {
                directory: {
                    path: '../public',
                    listing: true
                }
            }
        });

        server.route(routes.endpoints);
});
// ...

In one of my controllers when I write

fsPath.find('public', function(err, list){
    console.log(list);
});

I have an undefined result.

Thank's a lot for your help.

Move etags caching config to plugin options

hapi 9.0 drops support for etagsCacheMaxSize.

This is the old test.

    it('does not cache etags', function (done) {

        var server = new Hapi.Server({ files: { etagsCacheMaxSize: 0 } });
        server.register(Inert, Hoek.ignore);
        server.connection({ routes: { files: { relativeTo: __dirname } } });
        server.route({ method: 'GET', path: '/note', handler: { file: './file/note.txt' } });

        server.inject('/note', function (res1) {

            expect(res1.statusCode).to.equal(200);
            expect(res1.result).to.equal('Test');
            expect(res1.headers.etag).to.not.exist();

            server.inject('/note', function (res2) {

                expect(res2.statusCode).to.equal(200);
                expect(res2.result).to.equal('Test');
                expect(res2.headers.etag).to.not.exist();
                done();
            });
        });
    });

Failed to open file: EMFILE

I just got

Debug: internal, implementation, error 
     Error: Failed to open file: EMFILE, open '/home/***/src/assets/img/image.gif'

together with

Error: EMFILE, too many open files
     at /home/***/node_modules/good/lib/utils.js:24:26

I serve the img with reply.file().
Some googling told me to many files were open but not sure how to solve this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.