diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000..d3146d2 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,3 @@ +# see available options / defaults at https://github.com/probot/stale/#usage +daysUntilStale: 60 +daysUntilClose: 7 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b60b42e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +sudo: false +language: node_js +cache: + directories: + - node_modules +notifications: + email: false +node_js: + - '6' +before_install: + - npm i -g npm@^3.0.0 + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start +script: TEST_CLIENT=selenium:firefox npm test +before_script: + - npm prune +after_success: + - npm run semantic-release diff --git a/CAKEFILE b/CAKEFILE deleted file mode 100644 index 7288a0d..0000000 --- a/CAKEFILE +++ /dev/null @@ -1,9 +0,0 @@ -{print} = require 'util' -{spawn} = require 'child_process' - -task 'build', 'compile', -> - coffee = spawn 'coffee', ['-c', '-b', '-o', './lib', 'src'] - coffee.stderr.on 'data', (data) -> - process.stderr.write data.toString() - coffee.stdout.on 'data', (data) -> - print data.toString() \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ac00bc8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:8 + +RUN npm install -g corsproxy + +EXPOSE 1337 + +ENV CORSPROXY_HOST 0.0.0.0 + +ENTRYPOINT ["corsproxy"] \ No newline at end of file diff --git a/Procfile b/Procfile deleted file mode 100644 index 6f86b16..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node server.js \ No newline at end of file diff --git a/README.md b/README.md index 8133054..14a8219 100644 --- a/README.md +++ b/README.md @@ -1,49 +1,48 @@ -## Installation +# corsproxy -As a standalone tool: +> standalone CORS proxy - $ npm install -g corsproxy +[![Build Status](https://travis-ci.org/gr2m/CORS-Proxy.svg?branch=master)](https://travis-ci.org/gr2m/CORS-Proxy) +[![Dependency Status](https://david-dm.org/gr2m/CORS-Proxy.svg)](https://david-dm.org/gr2m/CORS-Proxy) +[![devDependency Status](https://david-dm.org/gr2m/CORS-Proxy/dev-status.svg)](https://david-dm.org/gr2m/CORS-Proxy#info=devDependencies) -As a dependency: +[![NPM](https://nodei.co/npm/corsproxy.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/corsproxy/) - $ npm install corsproxy +## Setup -## Running +``` +npm install -g corsproxy +corsproxy +# with custom port: CORSPROXY_PORT=1234 corsproxy +# with custom host: CORSPROXY_HOST=localhost corsproxy +# with debug server: DEBUG=1 corsproxy +# with custom payload max bytes set to 10MB (1MB by default): CORSPROXY_MAX_PAYLOAD=10485760 corsproxy +``` -Standalone: +### Docker - $ corsproxy - CORS Proxy started on localhost:9292 +It is also possible to run the cors proxy in a docker container: -Standalone with custom host/port: +``` +# Build image +docker build -t corsproxy . - $ corsproxy 0.0.0.0 1234 - CORS Proxy started on 0.0.0.0:1234 +# Run container +docker run -p 1337:1337 --name corsproxy -d corsproxy +``` -As a dependency: - - var cors_proxy = require("corsproxy"); - var http_proxy = require("http-proxy"); - http_proxy.createServer(cors_proxy).listen(1234); - -With custom target: +## Usage - var cors_proxy = require("corsproxy"); - var http_proxy = require("http-proxy"); - cors_proxy.options = { - target: { - host:"0.0.0.0", - port:5984 - } - }; - http_proxy.createServer(cors_proxy).listen(1234); +The cors proxy will start at http://localhost:1337. +To access another domain, use the domain name (including port) as the first folder, e.g. +- http://localhost:1337/localhost:3000/sign_in +- http://localhost:1337/my.domain.com/path/to/resource +- etc etc -## Usage +By default the cors proxy will only answer requests sent to localhost. To use another domain (e.g. machine name) set an enviroment variable CORSPROXY_HOST to the required value before launching. -The cors proxy will start at http://localhost:9292. To access another domain, use the domain name (including port) as the first folder, e.g. +## License - http://localhost:9292/localhost:3000/sign_in - http://localhost:9292/my.domain.com/path/to/resource - etc etc \ No newline at end of file +MIT diff --git a/bin/corsproxy b/bin/corsproxy new file mode 100755 index 0000000..c82f3a5 --- /dev/null +++ b/bin/corsproxy @@ -0,0 +1,120 @@ +#!/usr/bin/env node + +var Hapi = require('hapi') +var plugin = require('../index') +var good = require('good') +var loggerOptions = require('../lib/logger-options') + +var server = new Hapi.Server({}) +var port = parseInt(process.env.CORSPROXY_PORT || process.env.PORT || 1337, 10) +var host = (process.env.CORSPROXY_HOST || 'localhost'); +var maxPayload = parseInt(process.env.CORSPROXY_MAX_PAYLOAD || 1048576, 10) +var proxy = server.connection({ port: port, labels: ['proxy'], host: host}) + +server.register(require('inert'), function () {}) +server.register(require('h2o2'), function () {}) + +// cors plugin +server.register(plugin, { + select: ['proxy'] +}, function (error) { + if (error) server.log('error', error) +}) + +// logger plugin +server.register({ + register: good, + options: loggerOptions +}, function (error) { + if (error) server.log('error', error) +}) + +// proxy route +proxy.route({ + method: '*', + path: '/{host}/{path*}', + handler: { + proxy: { + passThrough: true, + mapUri: function (request, callback) { + request.host = request.params.host + request.path = request.path.substr(request.params.host.length + 1) + request.headers['host'] = request.host + var query = request.url.search ? request.url.search : '' + console.log('proxy to http://' + request.host + request.path) + callback(null, 'http://' + request.host + request.path + query, request.headers) + } + } + }, + config: { + payload: { + maxBytes: maxPayload + } + } +}) + +// default route +proxy.route({ + method: 'GET', + path: '/', + handler: { + file: 'public/index.html' + } +}) +proxy.route({ + method: 'GET', + path: '/favicon.ico', + handler: { + file: 'public/favicon.ico' + } +}) + +if (process.env.DEBUG) { + var testport = port + 1 + var test = server.connection({ port: testport, labels: ['test'], host: host }) + + server.register(require('vision'), function (error) { + if (error) { + throw error + } + + server.views({ + engines: { ejs: require('ejs') }, + path: 'public/test' + }) + }) + + test.route({ + method: 'GET', + path: '/favicon.ico', + handler: { + file: 'public/favicon.ico' + } + }) + test.route({ + method: 'GET', + path: '/test.json', + handler: { + file: 'public/test/test.json' + } + }) + + test.route({ + method: 'GET', + path: '/', + handler: function (request, reply) { + reply.view('index', { + proxyPort: proxy.info.port, + testPort: test.info.port + }) + } + }) + + server.log('info', 'Debug server starting at: ' + test.info.uri) +} + +server.start(function (error) { + if (error) server.log('error', error) + + server.log('info', 'CORS Proxy running at: ' + proxy.info.uri) +}) diff --git a/bin/index.js b/bin/index.js deleted file mode 100755 index d8858f3..0000000 --- a/bin/index.js +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env node -var corsproxy = require("../lib/corsproxy"); -var httpProxy = require("http-proxy"); - -var host = process.env.HOST || process.argv[2] || "127.0.0.1"; -var port = process.env.PORT || process.argv[3] || 9292; - -httpProxy.createServer(corsproxy).listen(port, host, function() { - console.log("CORS Proxy started on %s:%d", host, port); -}); diff --git a/bin/test-background b/bin/test-background new file mode 100755 index 0000000..8a7466f --- /dev/null +++ b/bin/test-background @@ -0,0 +1,48 @@ +#!/bin/bash + +set -e + +function cleanup { + set +e + + npm run kill_selenium + + echo "\nYou can safely ignore errors below, if you see any" + + # kill chrome driver + CHROMEDRIVER_PID=`ps -ef | grep chromedriver | grep -v grep | awk '{print $2}'` + if [ "$CHROMEDRIVER_PID" ] ; then + kill -9 $CHROMEDRIVER_PID + fi + # kill test app + APP_PID=`ps -ef | grep 'corsproxy' | grep -v grep | awk '{print $2}'` + if [ "$APP_PID" ] ; then + kill -9 $APP_PID + fi +} + +# always kill selenium, no matter if tests pass or exit +trap cleanup EXIT + +if [ ! -d "/tmp/sv-selenium" ]; then + echo 'Selenium not yet installed, downloading & installing ...' + npm run install_selenium_and_chromedriver +fi + +npm run start_selenium_with_chromedriver & +DEBUG=1 npm start & + +COUNTER=0 +until $(curl --output /dev/null --silent --head --fail http://localhost:1338); do + let COUNTER=COUNTER+1 + echo "Waiting for app ... $COUNTER / 60" + sleep 1 + if [[ COUNTER -eq 60 ]] ; then + echo "" + echo "App start timeout" + exit 1 + fi +done +echo 'App started :)' + +npm run test:mocha diff --git a/index.js b/index.js new file mode 100644 index 0000000..8cb231e --- /dev/null +++ b/index.js @@ -0,0 +1,12 @@ +var addCorsHeaders = require('hapi-cors-headers') +var pkg = require('./package.json') + +function corsPlugin (server, options, next) { + server.ext('onPreResponse', addCorsHeaders) + next() +} + +corsPlugin.attributes = { + pkg: pkg +} +module.exports = corsPlugin diff --git a/lib/corsproxy.js b/lib/corsproxy.js deleted file mode 100644 index fb6a331..0000000 --- a/lib/corsproxy.js +++ /dev/null @@ -1,66 +0,0 @@ -// Generated by CoffeeScript 1.4.0 -var http, httpProxy, url; - -url = require('url'); - -http = require('http'); - -httpProxy = require('http-proxy'); - -module.exports = function(req, res, proxy) { - var cors_headers, header, headers, host, hostname, ignore, key, path, port, target, target0, value, _i, _len, _ref, _ref1, _ref2; - if (req.headers['access-control-request-headers']) { - headers = req.headers['access-control-request-headers']; - } else { - headers = 'accept, accept-charset, accept-encoding, accept-language, authorization, content-length, content-type, host, origin, proxy-connection, referer, user-agent, x-requested-with'; - _ref = req.headers; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - header = _ref[_i]; - if (req.indexOf('x-') === 0) { - headers += ", " + header; - } - } - } - cors_headers = { - 'access-control-allow-methods': 'HEAD, POST, GET, PUT, PATCH, DELETE', - 'access-control-max-age': '86400', - 'access-control-allow-headers': headers, - 'access-control-allow-credentials': 'true', - 'access-control-allow-origin': req.headers.origin || '*' - }; - if (req.method === 'OPTIONS') { - console.log('responding to OPTIONS request'); - res.writeHead(200, cors_headers); - res.end(); - } else { - module.exports.options = module.exports.options || {}; - if (module.exports.options.target) { - target0 = url.parse(module.exports.options.target); - target = { - host: target0.hostname, - port: target0.port - }; - path = req.url; - req.headers.host = target0.hostname; - } else { - _ref1 = req.url.match(/\/([^\/]+)(.*)/), ignore = _ref1[0], hostname = _ref1[1], path = _ref1[2]; - _ref2 = hostname.split(':'), host = _ref2[0], port = _ref2[1]; - target = { - host: host, - port: port - }; - req.headers.host = hostname; - } - if (!target) { - res.write("Cannot determine target host\n"); - res.end(); - return; - } - for (key in cors_headers) { - value = cors_headers[key]; - res.setHeader(key, value); - } - req.url = path; - return proxy.proxyRequest(req, res, target); - } -}; diff --git a/lib/logger-options.js b/lib/logger-options.js new file mode 100644 index 0000000..7af9fcf --- /dev/null +++ b/lib/logger-options.js @@ -0,0 +1,12 @@ +var goodConsole = require('good-console') +module.exports = { + opsInterval: 1000, + reporters: [{ + reporter: goodConsole, + events: { + log: '*', + request: '*', + response: '*' + } + }] +} diff --git a/package.json b/package.json index 01ff738..edc16ac 100644 --- a/package.json +++ b/package.json @@ -1,28 +1,50 @@ { "name": "corsproxy", - "description": "standalone CORS proxy and library for your convenience", - "version": "0.2.9", - "main": "./lib/corsproxy.js", + "description": "standalone CORS proxy", + "main": "./index.js", "author": { "name": "Gregor Martynus", "email": "gregor@martynus.net" }, - "contributors": { - "name": "Jan Lehnardt", - "email": "jan@apache.org" - }, "dependencies": { - "http-proxy": "~0.8" + "ejs": "^2.3.3", + "good": "^6.3.0", + "good-console": "^5.0.3", + "h2o2": "^4.0.1", + "hapi": "^9.0.2", + "hapi-cors-headers": "^1.0.0", + "http-proxy": "~1.11", + "inert": "^3.0.1", + "vision": "^3.0.0" + }, + "devDependencies": { + "chai": "^3.2.0", + "chai-as-promised": "^5.1.0", + "colors": "^1.1.2", + "mocha": "^2.2.5", + "request": "^2.60.0", + "sauce-connect-launcher": "^0.12.0", + "semantic-release": "^4.0.3", + "standard": "^5.1.0", + "sv-selenium": "^0.2.6", + "wd": "^0.3.12" }, "bin": { - "corsproxy": "./bin/index.js" + "corsproxy": "./bin/corsproxy" }, - "engines": { - "node": ">=0.6.x", - "npm": ">=1.1.x" + "scripts": { + "install_selenium_and_chromedriver": "install_chromedriver && install_selenium", + "kill_selenium": "kill_selenium", + "semantic-release": "semantic-release pre && npm publish && semantic-release post", + "start": "./bin/corsproxy", + "start_app": "./bin/start-app", + "start_selenium_with_chromedriver": "start_selenium_with_chromedriver", + "test": "standard && ./bin/test-background", + "test:mocha": "mocha test/test-*.js" }, "repository": { "type": "git", "url": "https://github.com/gr2m/CORS-Proxy" - } -} \ No newline at end of file + }, + "license": "MIT" +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..85a4d9f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..3dd4c69 --- /dev/null +++ b/public/index.html @@ -0,0 +1,33 @@ + + +
+ ++ To add CORS headers to another URL, set the target URL as the path for the CORS proxy's URL. + For example, if you wan to access http://my.domain.com/path/to/resource, and the CORS proxy's + URL is http://localhost:1337, the full proxy URL is http://localhost:1337/my.domain.com/path/to/resource. +
+ +
+ You do not open http://localhost:1337/my.domain.com/path/to/resource directly in your browser,
+ instead open it through XMLHttpRequest.
+ For example, if you are using jQuery, you can do $.get('http://localhost:1337/my.domain.com/path/to/resource')
+
+ https://github.com/gr2m/CORS-Proxy +
+ + diff --git a/public/test/index.ejs b/public/test/index.ejs new file mode 100644 index 0000000..225b771 --- /dev/null +++ b/public/test/index.ejs @@ -0,0 +1,48 @@ + + + +var url = 'http://127.0.0.1:<%= testPort %>/test.json' +var request = new XMLHttpRequest(); +request.addEventListener('load', function () { + document.querySelector('#example-1 .result').textContent = 'Success' +}) +request.addEventListener('error', function () { + document.querySelector('#example-1 .result').textContent = 'Error' +}) +request.open('get', url, true) +request.setRequestHeader('Content-Type', 'application/json') +request.send()+ +
Result:
+var url = 'http://localhost:<%= proxyPort %>/127.0.0.1:<%= testPort %>/test.json' +var request = new XMLHttpRequest(); +request.addEventListener('load', function () { + document.querySelector('#example-2 .result').textContent = 'Success' +}) +request.addEventListener('error', function () { + document.querySelector('#example-2 .result').textContent = 'Error' +}) +request.open('get', url, true) +request.setRequestHeader('Content-Type', 'application/json') +request.send()+ +
Result:
+