Giter VIP home page Giter VIP logo

argo's Introduction

Argo

An extensible, asynchronous HTTP reverse proxy and origin server.

Examples

Adding Cross-Origin Resource Sharing

Setup the server:

var argo = require('argo');

argo()
  .use(function(handle) {
    handle('response', function(env, next) {
      env.response.setHeader('Access-Control-Allow-Origin', '*');
      next(env);
    });
  })
  .target('http://weather.yahooapis.com')
  .listen(1337);

Make a request:

$ curl -i http://localhost:1337/forecastrss?w=2467861

HTTP/1.1 200 OK
Date: Thu, 28 Feb 2013 20:55:03 GMT
Content-Type: text/xml;charset=UTF-8
Connection: keep-alive
Server: YTS/1.20.13
Access-Control-Allow-Origin: *
Content-Length: 2337

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<GiantXMLResponse/>

Serving an API Response

Setup the server:

var argo = require('argo');

argo()
  .get('^/dogs$', function(handle) {
    handle('request', function(env, next) {
      env.response.statusCode = 200;
      env.response.body = { dogs: ['Alfred', 'Rover', 'Dino'] };
      next(env);
    });
  })
  .listen(1337);

Make a request:

$ curl -i http://localhost:1337/dogs

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 34 
Date: Thu, 28 Feb 2013 20:44:46 GMT
Connection: keep-alive

{"dogs":["Alfred","Rover","Dino"]}

Install

$ npm install argo

Documentation

Usage

### handleFunction(type, [options], callback)
  • type: 'request' or 'response'

  • options: Mostly used for internal purposes. Optional.

  • callback(env, next): A request or response callback. env is an environment context that is passed to every handler, and next is a reference to the next function in the pipeline.

When the handler is complete and wishes to pass to the next function in the pipeline, it must call next(env).

### use(handleFunction)

handleFunction is used to set up request and response handlers.

argo()
  //For every request add 'X-Custom-Header' with value 'Yippee!'
  .use(function(handle) {
    handle('request', function(env, next) {
      env.request.headers['X-Custom-Header'] = 'Yippee!';
      next(env);
    });
  })
### use(package)

Alias for include(package).

### target(uri)

target is used for proxying requests to a backend server.

  • uri: a string pointing to the target URI.

Example:

argo()
  .target('http://weather.yahooapis.com')
### route(path, [options], handleFunction)
  • path: a regular expression used to match HTTP Request URI path.

  • options: an object with a methods property to filter HTTP methods (e.g., { methods: ['GET','POST'] }). Optional.

  • handleFunction: Same as in use.

Example:

argo()
  .route('^/greeting$', function(handle) {
    handle('request', function(env, next) {
      env.response.statusCode = 200;
      env.response.headers = { 'Content-Type': 'text/plain' };
      env.response.body = 'Hello World!';
 
      next(env);
    });
  })
#### get(path, handleFunction) #### post(path, handleFunction) #### put(path, handleFunction) #### head(path, handleFunction) #### del(path, handleFunction) #### options(path, handleFunction) #### trace(path, handleFunction) #### copy(path, handleFunction) #### lock(path, handleFunction) #### mkcol(path, handleFunction) #### move(path, handleFunction) #### propfind(path, handleFunction) #### proppatch(path, handleFunction) #### unlock(path, handleFunction) #### report(path, handleFunction) #### mkactivity(path, handleFunction) #### checkout(path, handleFunction) #### merge(path, handleFunction) #### msearch(path, handleFunction) #### notify(path, handleFunction) #### subscribe(path, handleFunction) #### unsubscribe(path, handleFunction) #### patch(path, handleFunction) #### search(path, handleFunction)

Method filters built on top of route. del and msearch correspond to the DELETE and M-SEARCH methods, respectively.

Example:

argo()
  .get('^/puppies$', function(handle) {
    handle('request', function(env, next) {
      env.response.body = JSON.stringify([{name: 'Sparky', breed: 'Fox Terrier' }]);
      next(env);
    });
  })
### map(path, [options], argoSegmentFunction)

map is used to delegate control to sub-Argo instances based on a request URI path.

  • path: a regular expression used to match the HTTP Request URI path.

  • options: an object with a methods property to filter HTTP methods (e.g., { methods: ['GET','POST'] }). Optional.

  • argoSegmentFunction: a function that is passed an instance of argo for additional setup.

Example:

argo()
  .map('^/payments', function(server) {
    server
      .use(oauth)
      .target('http://backend_payment_server');
  })
### include(package)
  • package: An object that contains a package property.

The package property is a function that takes an argo instance as a paramter and returns an object that contains a name and an install function.

Example:

var superPackage = function(argo) {
  return {
    name: 'Super Package',
    install: function() {
      argo
        .use(oauth)
        .route('^/super$', require('./super'));
    }
  };
};

argo()
  .include({ package: superPackage})
### listen(port)
  • port: A port on which the server should listen.
### Error Handling

Argo allows a special error handler for capturing state when an uncaught exception occurs.

argo()
  .use(function(handle) {
    handle('error', function(env, error, next) {
      console.log(error.message);
      env.response.statusCode = 500;
      env.response.body = 'Internal Server Error';
      next(env);
      process.exit();
    });
  })
  .get('^/$', function(handle) {
    handle('request', function(env, next) {
      env.response.body = 'Hello World!';
      next(env);
    });
  })
  .get('^/explode$', function(handle) {
    handle('request', function(env, next) {
      setImmediate(function() { throw new Error('Ahoy!'); });
    });
  })
  .listen(3000);

Unlike other named pipelines, there should be only one error handler assigned to an Argo server. It is recommended to exit the process once an error has been handled. This feature uses domains.

See cluster.js for an example of using error handling to restart workers in a cluster.

Tests

Unit tests:

$ npm test

Test Coverage:

$ npm run-script coverage

License

MIT

argo's People

Contributors

adammagaluk avatar kevinswiber avatar mdobson avatar ttahmouch avatar wooliet avatar ypomortsev 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

Watchers

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

argo's Issues

Wrap ServerRequest and ServerResponse in env.request, env.response.

Recently, a change was committed (cf2ac9d) to stop monkey-patching ServerRequest and ServerResponse. The result is an API that looks like:

function(env, next) {
  env.response.statusCode = 200;
  env.response.headers = { 'Content-Type': 'text/plain' };
  env.responseBody = 'Aloha, Gaia!';

  next(env);
}

That API mismatch is unpretty. The goal should be to create a change so that the API does NOT monkey-patch, but instead wraps ServerResponse (in this case). The end result should look something like:

function(env, next) {
  env.response.statusCode = 200;
  env.response.headers = { 'Content-Type': 'text/plain' };
  env.response.body = 'Aloha, Gaia!';

  next(env);
}

Ah, that's better. BONUS: Add a shortcut that allows something like:

env.response.set([200, { 'Content-Type': 'text/plain' }, 'Aloha, Gaia!']);

But better than that (hopefully).

Create body parser middleware to spool the body.

Somewhat on the fence with this...

It would be easier for developers to work with the response body after it has been fully retrieved. This happens at the expense of asynchronous piping from the target through the proxy to the client.

Will likely need this for the request and the response body.

Proxy when matching a url?

Hi @kevinswiber,

I would like to use Argo to proxy calls to my API when a specific url is matched.

For example, proxy: /api/v2/datasets -> example.com/api/v2/datasets

Its clear how to do this generically for all urls, but how do I trigger proxy only when url contains /api/v2?

I've been tracking this library since your talk in Toronto. You mentioned that Apigee is working on something for Node.js. Any updates on that?

Make Argo Stream.pipe-able.

var http = require('http');
var argo = require('argo');

var proxy = argo().target('http://weather.yahooapis.com');

http.createServer(function(req, res) {
  req.pipe(proxy).pipe(res);
}).listen(1337);

That might not give us what we want. Maybe something like...

var http = require('http');
var argo = require('argo');
var streamer = require('argo-streamer');

var proxy = argo().target('http://weather.yahooapis.com');

http.createServer(function(req, res) {
  var stream = streamer(proxy, req, res);
  req.pipe(stream).pipe(res);
}).listen(1337);

Create an "error" pipeline handler.

Support this:

argo()
  .use(function(handle) {
    handle('error', function(env, next) {
      response.statusCode = 500;
      next(env);
    });
  })
  1. Catch an error.
  2. env.argo.pipeline.siphon('error', env, next);

Add correlation ID for requests and a sequence number for trace output.

This is needed for tracing to determine which trace lines are correlated with the same request and to record the time sequence. Async communication can create a partially ordered queue. These proposed changes will help to resolve ordering by the client.

Example:

{
  "requestId": "9cff0456-84b3-401a-89f6-bc9eae559369",
  "sequenceNumber": 1
}

Hangs when using the proxy.

Argo hangs when using the proxy. This appears to be a problem introduced by the switch to Streams 2 support.

Thanks for the heads up, @timanglade! Next time, don't hesitate to create an issue. For important fixes like this, @mdobson and I tend to act fast.

[support] pointers on how to alter body on proxies request to server?

First off, thanks so much for the rad product!

Totally my fault here, but I'm going a little batty trying to sort this out, and would totally appreciate any pointers in the right direction :)

https://github.com/patcon/polis-api-proxy/blob/master/path_parameterization.js#L33

It's pretty messy, but I've intercepted POST requests, caught the body, and merged my changes in, but I can't seem to sort out the best way to "pass this along"..! :)

Collapsed Forwarding

Protect the backend!

Prevent the backend server from getting slammed with a 1,000,000 requests while waiting for the cache to fill.

Broken links in documentation

Gzip middleware

  1. Sniff Accept-Encoding.
  2. Modify (request|response).getBody so it auto-inflates when accessed. (proxies matter)
  3. Re-deflate prior to serving.

Undefined behavior: Adding .route before .use

Argo currently has undefined behavior when setting up a pipeline with .route before .use.

Example:

var assert = require('assert');
var argo = require('argo');

argo()
  .route('/hello', function(handle) {
    handle('request', function(env, next) {
      assert(env.test, 'success'); // FAILS
      next(env);
    });
  })
  .use(function(handle) {
    handle('request', function(env, next) {
      env.test = 'success';
      next(env);
    });
  })
  .listen(3000);

This will currently produce an AssertionError.

The behavior should be defined so that pipelines are executed in this order:

  1. Middleware Request Pipeline
  2. Route Request Pipeline
  3. Proxy Target
  4. Route Response Pipeline
  5. Middleware Response Pipeline

argo dies if the target request fails

In perf testing I found it's quite easy to get argo to die if the target can't be reached or if a new request can't connect, do the DNS lookup, or whatever. That makes some benchmarks run for a very short time. argo should at least have an 'error' handler on the HTTP request and return an error to the client, and eventually have a whole error pipeline.

When proxying to target Host header drops port

We're using parsed.hostname but it should be parsed.host for the Host header.

argo/argo.js

Line 547 in 9e39f70

options.headers['Host'] = options.hostname;

The server at localhost:1337 will get a request with the Host header set to only localhost not localhost:1337.

argo()
  .target('http://localhost:1337')
  .listen(3000)

Argo crashes on header copying for node v0.11

Steps to repro

  1. Use Nodejs 0.11 with this code.
var argo = require('argo');

argo()
  .use(function(handle) {
    handle("response", function(env, next) {
      env.response.statusCode = 200;
      env.response.headers = { 'content-type':'text/plain'};
      env.response.body = 'Hello';
      next(env);
    });
  })
  .listen(3000);

Stack trace

_http_incoming.js:187
        if (util.isUndefined(dest[field])) dest[field] = value;
                                 ^
TypeError: Cannot read property 'host' of undefined
    at IncomingMessage._addHeaderLine (_http_incoming.js:187:34)
    at IncomingMessage.incoming._addHeaderLine (/Users/ApigeeCorporation/node/paas-router/node_modules/argo/argo.js:36:22)
    at IncomingMessage._addHeaderLines (_http_incoming.js:131:12)
    at HTTPParser.parserOnHeadersComplete (_http_common.js:92:19)
    at Socket.socketOnData (_http_server.js:358:22)
    at Socket.EventEmitter.emit (events.js:98:17)
    at readableAddChunk (_stream_readable.js:156:16)
    at Socket.Readable.push (_stream_readable.js:123:10)
    at TCP.onread (net.js:509:20)

I've confirmed that this code works with node 0.10

Tests Broken

We've got tests that are broken in Argo. Mostly around routing.

Clean up route handler magic

There's a lot of duplication in platform.js when it comes to figuring out the route handlers. Perhaps caching the handlers would make sense.

Route response handlers are executed multiple times when multiple routes are defined.

Example:

var assert = require('assert');
var argo = require('argo');

argo()
  .get('^/hello$', function(handle) {
    handle('request', function(env, next) {
      next(env);
    });
  })
  .get('^/world$', function(handle) {
    handle('response', function(env, next) {
      env.count = env.count || 0;
      env.count++;

      assert.equal(env.count, 1);
      next(env);
    });
  })
  .listen(3000);

A call to /world results in AssertionError: 2 == 1.

Allow for nested proxy configurations.

I thought there was an issue for this, but apparently not. I'll add one in retrospect, despite the lack of value implied. :)

We need a way to nest complex proxy configs. "When going to /api, use CORS middleware."

Using .map would be fitting.

Example:

var argo = require('../');
var cors = require('./cors');

argo()
  .use(cors)
  .get('/outer', function(addHandler) {
    addHandler('request', function(env, next) {
      env.response.body = 'Outer scope!';
      next(env);
    });
  })
  .map('/web', function(proxy) {
    proxy 
      .use(function(addHandler) {
        addHandler('response', function(env, next) {
          env.response.headers['X-Stuff'] = 'Yep';
          next(env);
        });
      })
      .get('/greeting', function(addHandler) {
        addHandler('request', function(env, next) {
          env.response.body = 'Hello, world!';
          next(env);
        }); 
      });
  })
  .listen(process.env.PORT || 3000);

See commit c25a25d.

Mapped proxies don't wind/unwind target.

When nesting proxies, such as...

argo()
  .map('/locations', function(proxy) {
    proxy
      .target('http://4sq/locations');
  })
  .target('http://foodcity/products')
  .listen(1337)

...the Frame of current execution doesn't include the responsibility of target execution. Therefore, hitting http://localhost:3000/locations will execute the target request twice.

Fix, yo.

Caching

Cache it on the proxy, yo.

Mapped servers append their endpoint to a proxy url

This code sample appends the mapped endpoint to the target URL.

0.0.0.0:3000/mtraining proxies to api.usergrid.com/mtraining/sandbox/mtrainings

var argo = require('argo'),
    router = require('argo-url-router');

var server = argo();

server.map('/mtraining', function(server) {
  server.target('http://api.usergrid.com/mtraining/sandbox');
});

server.map('/mdobson', function(server) {
  server.target('http://api.usergrid.com/mdobson/sandbox');
});

server.listen(3000);

Support routing via HTTP methods, as well.

proxy.route(route, function(handlers) {}); // all
proxy.get(route, function(handlers) {});
proxy.route(route, { methods: ['GET','HEAD'] }, function(handlers) {}); // could allow for extensions like WebDAV.

Get to 100% Test Coverage

Psst. Hey, Kevin, stop being lazy.

Adding a placeholder for testing.

I've been working to get test coverage up. Metrics are calculated using jscoverage. This is only one metric for quality, and it's pretty weak (IMHO), but people pay attention to it.

The end goal is to have super thorough testing (without changing the structure of code to only accommodate better testing).

We're at 85% at this very moment on my local branch (for Argo core, not the OAuth package).

I should get to 100% today. This is pretty important.

Using a combination of route + target no longer executes route.

Not sure what happened, but this no longer works:

var argo = require('argo');
var router = require('argo-url-router');

argo()
  .use(router)
  .get('/hello', function(handle) {
    handle('request', function(env, next) {
      env.response.body = 'World.';
      env.target.skip = true;
      next(env);
    });
  })
  .target('https://api.usergrid.com')
  .listen(3000);

Expected output of hitting http://localhost:3000/hello is "World." Instead, the request gets proxied to the target.

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.