mirror of
https://github.com/alibaba/anyproxy.git
synced 2025-04-23 20:31:25 +00:00
Merge branch '3.10.3beta1' into 'master'
Add test cases for the proxy, and do some tiny fixes. the fixes are: 1. add "content-type" in headers for when dealing with localresponse 2. make a more accurate tip for throttle rate when lower than 1 3. make the crtMgr funcionality a more independent one 4. uppercase the request header before sending it out update the tip See merge request !2
This commit is contained in:
commit
530491d2a9
245
.eslintrc.js
Normal file
245
.eslintrc.js
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
module.exports = {
|
||||||
|
// http://eslint.org/docs/rules/
|
||||||
|
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"arrowFunctions": false, // enable arrow functions
|
||||||
|
"binaryLiterals": false, // enable binary literals
|
||||||
|
"blockBindings": false, // enable let and const (aka block bindings)
|
||||||
|
"classes": false, // enable classes
|
||||||
|
"defaultParams": false, // enable default function parameters
|
||||||
|
"destructuring": false, // enable destructuring
|
||||||
|
"forOf": false, // enable for-of loops
|
||||||
|
"generators": false, // enable generators
|
||||||
|
"modules": false, // enable modules and global strict mode
|
||||||
|
"objectLiteralComputedProperties": false, // enable computed object literal property names
|
||||||
|
"objectLiteralDuplicateProperties": false, // enable duplicate object literal properties in strict mode
|
||||||
|
"objectLiteralShorthandMethods": false, // enable object literal shorthand methods
|
||||||
|
"objectLiteralShorthandProperties": false, // enable object literal shorthand properties
|
||||||
|
"octalLiterals": false, // enable octal literals
|
||||||
|
"regexUFlag": false, // enable the regular expression u flag
|
||||||
|
"regexYFlag": false, // enable the regular expression y flag
|
||||||
|
"restParams": false, // enable the rest parameters
|
||||||
|
"spread": false, // enable the spread operator
|
||||||
|
"superInFunctions": false, // enable super references inside of functions
|
||||||
|
"templateStrings": false, // enable template strings
|
||||||
|
"unicodeCodePointEscapes": false, // enable code point escapes
|
||||||
|
"globalReturn": false, // allow return statements in the global scope
|
||||||
|
"jsx": false // enable JSX
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
|
||||||
|
"env": {
|
||||||
|
"browser": true, // browser global variables.
|
||||||
|
"node": false, // Node.js global variables and Node.js-specific rules.
|
||||||
|
"worker": false, // web workers global variables.
|
||||||
|
"amd": true, // defines require() and define() as global variables as per the amd spec.
|
||||||
|
"mocha": false, // adds all of the Mocha testing global variables.
|
||||||
|
"jasmine": true, // adds all of the Jasmine testing global variables for version 1.3 and 2.0.
|
||||||
|
"phantomjs": false, // phantomjs global variables.
|
||||||
|
"jquery": false, // jquery global variables.
|
||||||
|
"prototypejs": false, // prototypejs global variables.
|
||||||
|
"shelljs": false, // shelljs global variables.
|
||||||
|
"meteor": false, // meteor global variables.
|
||||||
|
"mongo": false, // mongo global variables.
|
||||||
|
"applescript": false, // applescript global variables.
|
||||||
|
"es6": true, // enable all ECMAScript 6 features except for modules.
|
||||||
|
},
|
||||||
|
|
||||||
|
"globals": {
|
||||||
|
"goog": true,
|
||||||
|
"module": true,
|
||||||
|
"exports": true,
|
||||||
|
"__dirname": true,
|
||||||
|
"process": true
|
||||||
|
},
|
||||||
|
|
||||||
|
"plugins": [
|
||||||
|
// e.g. "react" (must run `npm install eslint-plugin-react` first)
|
||||||
|
],
|
||||||
|
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
// Possible Errors
|
||||||
|
"comma-dangle": 0, // disallow trailing commas in object literals
|
||||||
|
"no-cond-assign": 0, // disallow assignment in conditional expressions
|
||||||
|
"no-console": 0, // disallow use of console (off by default in the node environment)
|
||||||
|
"no-constant-condition": 0, // disallow use of constant expressions in conditions
|
||||||
|
"no-control-regex": 0, // disallow control characters in regular expressions
|
||||||
|
"no-debugger": 0, // disallow use of debugger
|
||||||
|
"no-dupe-args": 0, // disallow duplicate arguments in functions
|
||||||
|
"no-dupe-keys": 0, // disallow duplicate keys when creating object literals
|
||||||
|
"no-duplicate-case": 0, // disallow a duplicate case label
|
||||||
|
"no-empty-character-class": 0, // disallow the use of empty character classes in regular expressions
|
||||||
|
"no-empty": 0, // disallow empty statements
|
||||||
|
"no-ex-assign": 0, // disallow assigning to the exception in a catch block
|
||||||
|
"no-extra-boolean-cast": 0, // disallow double-negation boolean casts in a boolean context
|
||||||
|
"no-extra-parens": 0, // disallow unnecessary parentheses (off by default)
|
||||||
|
"no-extra-semi": 1, // disallow unnecessary semicolons
|
||||||
|
"no-func-assign": 0, // disallow overwriting functions written as function declarations
|
||||||
|
"no-inner-declarations": 2, // disallow function or variable declarations in nested blocks
|
||||||
|
"no-invalid-regexp": 0, // disallow invalid regular expression strings in the RegExp constructor
|
||||||
|
"no-irregular-whitespace": 0, // disallow irregular whitespace outside of strings and comments
|
||||||
|
"no-negated-in-lhs": 0, // disallow negation of the left operand of an in expression
|
||||||
|
"no-obj-calls": 0, // disallow the use of object properties of the global object (Math and JSON) as functions
|
||||||
|
"no-regex-spaces": 0, // disallow multiple spaces in a regular expression literal
|
||||||
|
"no-reserved-keys": 0, // disallow reserved words being used as object literal keys (off by default)
|
||||||
|
"no-sparse-arrays": 0, // disallow sparse arrays
|
||||||
|
"no-unreachable": 0, // disallow unreachable statements after a return, throw, continue, or break statement
|
||||||
|
"use-isnan": 0, // disallow comparisons with the value NaN
|
||||||
|
"valid-jsdoc": 0, // Ensure JSDoc comments are valid (off by default)
|
||||||
|
"valid-typeof": 0, // Ensure that the results of typeof are compared against a valid string
|
||||||
|
"no-unexpected-multiline": 0, // Avoid code that looks like two expressions but is actually one (off by default)
|
||||||
|
|
||||||
|
|
||||||
|
// Best Practices
|
||||||
|
"accessor-pairs": 0, // enforces getter/setter pairs in objects (off by default)
|
||||||
|
"block-scoped-var": 0, // treat var statements as if they were block scoped (off by default)
|
||||||
|
"complexity": 0, // specify the maximum cyclomatic complexity allowed in a program (off by default)
|
||||||
|
"consistent-return": 0, // require return statements to either always or never specify values
|
||||||
|
"curly": 2, // specify curly brace conventions for all control statements
|
||||||
|
"default-case": 0, // require default case in switch statements (off by default)
|
||||||
|
"dot-notation": 0, // encourages use of dot notation whenever possible
|
||||||
|
"dot-location": 0, // enforces consistent newlines before or after dots (off by default)
|
||||||
|
"eqeqeq": 0, // require the use of === and !==
|
||||||
|
"guard-for-in": 0, // make sure for-in loops have an if statement (off by default)
|
||||||
|
"no-alert": 0, // disallow the use of alert, confirm, and prompt
|
||||||
|
"no-caller": 0, // disallow use of arguments.caller or arguments.callee
|
||||||
|
"no-div-regex": 0, // disallow division operators explicitly at beginning of regular expression (off by default)
|
||||||
|
"no-else-return": 0, // disallow else after a return in an if (off by default)
|
||||||
|
"no-empty-label": 0, // disallow use of labels for anything other then loops and switches
|
||||||
|
"no-eq-null": 0, // disallow comparisons to null without a type-checking operator (off by default)
|
||||||
|
"no-eval": 2, // disallow use of eval()
|
||||||
|
"no-extend-native": 2, // disallow adding to native types
|
||||||
|
"no-extra-bind": 0, // disallow unnecessary function binding
|
||||||
|
"no-fallthrough": 0, // disallow fallthrough of case statements
|
||||||
|
"no-floating-decimal": 0, // disallow the use of leading or trailing decimal points in numeric literals (off by default)
|
||||||
|
"no-implied-eval": 0, // disallow use of eval()-like methods
|
||||||
|
"no-iterator": 0, // disallow usage of __iterator__ property
|
||||||
|
"no-labels": 0, // disallow use of labeled statements
|
||||||
|
"no-lone-blocks": 0, // disallow unnecessary nested blocks
|
||||||
|
"no-loop-func": 0, // disallow creation of functions within loops
|
||||||
|
"no-multi-spaces": 0, // disallow use of multiple spaces
|
||||||
|
"no-multi-str": 0, // disallow use of multiline strings
|
||||||
|
"no-native-reassign": 0, // disallow reassignments of native objects
|
||||||
|
"no-new-func": 0, // disallow use of new operator for Function object
|
||||||
|
"no-new-wrappers": 2, // disallows creating new instances of String, Number, and Boolean
|
||||||
|
"no-new": 0, // disallow use of new operator when not part of the assignment or comparison
|
||||||
|
"no-octal-escape": 0, // disallow use of octal escape sequences in string literals, such as var foo = "Copyright \251";
|
||||||
|
"no-octal": 0, // disallow use of octal literals
|
||||||
|
"no-param-reassign": 0, // disallow reassignment of function parameters (off by default)
|
||||||
|
"no-process-env": 0, // disallow use of process.env (off by default)
|
||||||
|
"no-proto": 0, // disallow usage of __proto__ property
|
||||||
|
"no-redeclare": 0, // disallow declaring the same variable more then once
|
||||||
|
"no-return-assign": 0, // disallow use of assignment in return statement
|
||||||
|
"no-script-url": 0, // disallow use of javascript: urls
|
||||||
|
"no-self-compare": 0, // disallow comparisons where both sides are exactly the same (off by default)
|
||||||
|
"no-sequences": 0, // disallow use of comma operator
|
||||||
|
"no-throw-literal": 0, // restrict what can be thrown as an exception (off by default)
|
||||||
|
"no-unused-expressions": 0, // disallow usage of expressions in statement position
|
||||||
|
"no-void": 0, // disallow use of void operator (off by default)
|
||||||
|
"no-warning-comments": 0, // disallow usage of configurable warning terms in comments, e.g. TODO or FIXME (off by default)
|
||||||
|
"no-with": 2, // disallow use of the with statement
|
||||||
|
"radix": 0, // require use of the second argument for parseInt() (off by default)
|
||||||
|
"vars-on-top": 0, // requires to declare all vars on top of their containing scope (off by default)
|
||||||
|
"wrap-iife": 0, // require immediate function invocation to be wrapped in parentheses (off by default)
|
||||||
|
"yoda": 0, // require or disallow Yoda conditions
|
||||||
|
|
||||||
|
|
||||||
|
// Strict Mode
|
||||||
|
"strict": 0, // controls location of Use Strict Directives
|
||||||
|
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
"no-catch-shadow": 0, // disallow the catch clause parameter name being the same as a variable in the outer scope (off by default in the node environment)
|
||||||
|
"no-delete-var": 0, // disallow deletion of variables
|
||||||
|
"no-label-var": 0, // disallow labels that share a name with a variable
|
||||||
|
"no-shadow": 0, // disallow declaration of variables already declared in the outer scope
|
||||||
|
"no-shadow-restricted-names": 0, // disallow shadowing of names such as arguments
|
||||||
|
"no-undef": 2, // disallow use of undeclared variables unless mentioned in a /*global */ block
|
||||||
|
"no-undef-init": 0, // disallow use of undefined when initializing variables
|
||||||
|
"no-undefined": 0, // disallow use of undefined variable (off by default)
|
||||||
|
"no-unused-vars": 0, // disallow declaration of variables that are not used in the code
|
||||||
|
"no-use-before-define": 0, // disallow use of variables before they are defined
|
||||||
|
|
||||||
|
|
||||||
|
// Node.js
|
||||||
|
"handle-callback-err": 0, // enforces error handling in callbacks (off by default) (on by default in the node environment)
|
||||||
|
"no-mixed-requires": 0, // disallow mixing regular variable and require declarations (off by default) (on by default in the node environment)
|
||||||
|
"no-new-require": 0, // disallow use of new operator with the require function (off by default) (on by default in the node environment)
|
||||||
|
"no-path-concat": 0, // disallow string concatenation with __dirname and __filename (off by default) (on by default in the node environment)
|
||||||
|
"no-process-exit": 0, // disallow process.exit() (on by default in the node environment)
|
||||||
|
"no-restricted-modules": 0, // restrict usage of specified node modules (off by default)
|
||||||
|
"no-sync": 0, // disallow use of synchronous methods (off by default)
|
||||||
|
|
||||||
|
|
||||||
|
// Stylistic Issues
|
||||||
|
"array-bracket-spacing": [2, "never"], // enforce spacing inside array brackets (off by default)
|
||||||
|
"brace-style": 0, // enforce one true brace style (off by default)
|
||||||
|
"camelcase": 0, // require camel case names
|
||||||
|
"comma-spacing": 0, // enforce spacing before and after comma
|
||||||
|
"comma-style": 0, // enforce one true comma style (off by default)
|
||||||
|
"computed-property-spacing": 0, // require or disallow padding inside computed properties (off by default)
|
||||||
|
"consistent-this": 0, // enforces consistent naming when capturing the current execution context (off by default)
|
||||||
|
"eol-last": 0, // enforce newline at the end of file, with no multiple empty lines
|
||||||
|
"func-names": 0, // require function expressions to have a name (off by default)
|
||||||
|
"func-style": 0, // enforces use of function declarations or expressions (off by default)
|
||||||
|
"indent": [2, 4], // this option sets a specific tab width for your code (off by default)
|
||||||
|
"key-spacing": 0, // enforces spacing between keys and values in object literal properties
|
||||||
|
"lines-around-comment": 0, // enforces empty lines around comments (off by default)
|
||||||
|
"linebreak-style": 0, // disallow mixed 'LF' and 'CRLF' as linebreaks (off by default)
|
||||||
|
"max-nested-callbacks": 0, // specify the maximum depth callbacks can be nested (off by default)
|
||||||
|
"new-cap": 0, // require a capital letter for constructors
|
||||||
|
"new-parens": 0, // disallow the omission of parentheses when invoking a constructor with no arguments
|
||||||
|
"new-parens": 0, // disallow the omission of parentheses when invoking a constructor with no arguments
|
||||||
|
"newline-after-var": 0, // allow/disallow an empty newline after var statement (off by default)
|
||||||
|
"no-array-constructor": 2, // disallow use of the Array constructor
|
||||||
|
"no-continue": 0, // disallow use of the continue statement (off by default)
|
||||||
|
"no-inline-comments": 0, // disallow comments inline after code (off by default)
|
||||||
|
"no-lonely-if": 0, // disallow if as the only statement in an else block (off by default)
|
||||||
|
"no-mixed-spaces-and-tabs": 2, // disallow mixed spaces and tabs for indentation
|
||||||
|
"no-multiple-empty-lines": 0, // disallow multiple empty lines (off by default)
|
||||||
|
"no-nested-ternary": 0, // disallow nested ternary expressions (off by default)
|
||||||
|
"no-new-object": 2, // disallow use of the Object constructor
|
||||||
|
"no-spaced-func": 0, // disallow space between function identifier and application
|
||||||
|
"no-ternary": 0, // disallow the use of ternary operators (off by default)
|
||||||
|
"no-trailing-spaces": 0, // disallow trailing whitespace at the end of lines
|
||||||
|
"no-underscore-dangle": 0, // disallow dangling underscores in identifiers
|
||||||
|
"object-curly-spacing": [2, "always"], // require or disallow padding inside curly braces (off by default)
|
||||||
|
"one-var": 0, // allow just one var statement per function (off by default)
|
||||||
|
"operator-assignment": 0, // require assignment operator shorthand where possible or prohibit it entirely (off by default)
|
||||||
|
"operator-linebreak": 0, // enforce operators to be placed before or after line breaks (off by default)
|
||||||
|
"padded-blocks": 0, // enforce padding within blocks (off by default)
|
||||||
|
"quote-props": 0, // require quotes around object literal property names (off by default)
|
||||||
|
"quotes": 0, // specify whether double or single quotes should be used
|
||||||
|
"semi-spacing": 0, // enforce spacing before and after semicolons
|
||||||
|
"semi": [2, "always"], // require or disallow use of semicolons instead of ASI
|
||||||
|
"sort-vars": 0, // sort variables within the same declaration block (off by default)
|
||||||
|
"space-after-keywords": 0, // require a space after certain keywords (off by default)
|
||||||
|
"space-before-blocks": 0, // require or disallow space before blocks (off by default)
|
||||||
|
"space-before-function-paren": 0, // require or disallow space before function opening parenthesis (off by default)
|
||||||
|
"space-in-parens": 0, // require or disallow spaces inside parentheses (off by default)
|
||||||
|
"space-infix-ops": 0, // require spaces around operators
|
||||||
|
"space-return-throw-case": 0, // require a space after return, throw, and case
|
||||||
|
"space-unary-ops": 0, // require or disallow spaces before/after unary operators (words on by default, nonwords off by default)
|
||||||
|
"spaced-comment": 0, // require or disallow a space immediately following the // or /* in a comment (off by default)
|
||||||
|
"wrap-regex": 0, // require regex literals to be wrapped in parentheses (off by default)
|
||||||
|
|
||||||
|
|
||||||
|
// ECMAScript 6
|
||||||
|
"constructor-super": 0, // verify super() callings in constructors (off by default)
|
||||||
|
"generator-star-spacing": 0, // enforce the spacing around the * in generator functions (off by default)
|
||||||
|
"no-this-before-super": 0, // disallow to use this/super before super() calling in constructors (off by default)
|
||||||
|
"no-var": 0, // require let or const instead of var (off by default)
|
||||||
|
"object-shorthand": 0, // require method and property shorthand syntax for object literals (off by default)
|
||||||
|
"prefer-const": 0, // suggest using of const declaration for variables that are never modified after declared (off by default)
|
||||||
|
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
"max-depth": 0, // specify the maximum depth that blocks can be nested (off by default)
|
||||||
|
"max-len": [1, 120, 2], // specify the maximum length of a line in your program (off by default)
|
||||||
|
"max-params": 0, // limits the number of parameters that can be used in the function declaration. (off by default)
|
||||||
|
"max-statements": 0, // specify the maximum number of statement allowed in a function (off by default)
|
||||||
|
"no-bitwise": 0, // disallow use of bitwise operators (off by default)
|
||||||
|
"no-plusplus": 0 // disallow use of unary operators, ++ and -- (off by default)
|
||||||
|
}
|
||||||
|
}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,3 +25,4 @@ coverage
|
|||||||
build/Release
|
build/Release
|
||||||
node_modules
|
node_modules
|
||||||
.lock-wscript
|
.lock-wscript
|
||||||
|
temp
|
12
jasmine.json
Normal file
12
jasmine.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"spec_dir": "test",
|
||||||
|
"spec_files": [
|
||||||
|
"**/*[sS]pec.js"
|
||||||
|
],
|
||||||
|
"helpers": [
|
||||||
|
"../node_modules/babel-register/lib/node.js",
|
||||||
|
"../node_modules/babel-polyfill/dist/polyfill.js"
|
||||||
|
],
|
||||||
|
"stopSpecOnExpectationFailure": false,
|
||||||
|
"random": false
|
||||||
|
}
|
217
lib/certMgr.js
217
lib/certMgr.js
@ -1,174 +1,81 @@
|
|||||||
var exec = require('child_process').exec,
|
var logUtil = require('./log');
|
||||||
spawn = require('child_process').spawn,
|
var util = require('./util');
|
||||||
path = require("path"),
|
var color = require('colorful');
|
||||||
fs = require("fs"),
|
var EasyCert = require('node-easy-cert');
|
||||||
os = require("os"),
|
var exec = require('child_process').exec;
|
||||||
color = require('colorful'),
|
var path = require('path');
|
||||||
readline = require('readline'),
|
var readline = require('readline');
|
||||||
util = require('./util'),
|
|
||||||
logUtil = require("./log"),
|
|
||||||
certGenerator = require("./certGenerator"),
|
|
||||||
asyncTask = require("async-task-mgr");
|
|
||||||
|
|
||||||
var isWin = /^win/.test(process.platform),
|
var isWin = /^win/.test(process.platform);
|
||||||
certDir = path.join(util.getUserHome(),"/.anyproxy_certs/"),
|
var options = {
|
||||||
rootCAcrtFilePath = path.join(certDir,"rootCA.crt"),
|
rootDirPath: util.getUserHome() + '/.anyproxy_certs',
|
||||||
rootCAkeyFilePath = path.join(certDir,"rootCA.key"),
|
defaultCertAttrs: [
|
||||||
createCertTaskMgr = new asyncTask();
|
{ name: 'countryName', value: 'CN' },
|
||||||
|
{ name: 'organizationName', value: 'AnyProxy' },
|
||||||
|
{ shortName: 'ST', value: 'SH' },
|
||||||
|
{ shortName: 'OU', value: 'AnyProxy SSL Proxy' }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
if(!fs.existsSync(certDir)){
|
var easyCert = new EasyCert(options);
|
||||||
try{
|
var crtMgr = util.merge({}, easyCert);
|
||||||
fs.mkdirSync(certDir,0777);
|
|
||||||
}catch(e){
|
|
||||||
logUtil.printLog("===========", logUtil.T_ERR);
|
|
||||||
logUtil.printLog("failed to create cert dir ,please create one by yourself - " + certDir, logUtil.T_ERR);
|
|
||||||
logUtil.printLog("this error will not block main thread unless you use https-related features in anyproxy", logUtil.T_ERR);
|
|
||||||
logUtil.printLog("===========", logUtil.T_ERR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var cache_rootCACrtFileContent, cache_rootCAKeyFileContent;
|
// catch specified error, such as ROOT_CA_NOT_EXISTS
|
||||||
function getCertificate(hostname,certCallback){
|
crtMgr.getCertificate = function (host, cb) {
|
||||||
checkRootCA();
|
easyCert.getCertificate(host, (error, keyContent, crtContent) => {
|
||||||
var keyFile = path.join(certDir , "__hostname.key".replace(/__hostname/,hostname) ),
|
if (error === 'ROOT_CA_NOT_EXISTS') {
|
||||||
crtFile = path.join(certDir , "__hostname.crt".replace(/__hostname/,hostname) );
|
util.showRootInstallTip();
|
||||||
|
process.exit(0);
|
||||||
if(!cache_rootCACrtFileContent || !cache_rootCAKeyFileContent){
|
return;
|
||||||
cache_rootCACrtFileContent = fs.readFileSync(rootCAcrtFilePath, {encoding: 'utf8'});
|
|
||||||
cache_rootCAKeyFileContent = fs.readFileSync(rootCAkeyFilePath, {encoding: 'utf8'});
|
|
||||||
}
|
|
||||||
|
|
||||||
createCertTaskMgr.addTask(hostname,function(callback){
|
|
||||||
if(!fs.existsSync(keyFile) || !fs.existsSync(crtFile)){
|
|
||||||
try{
|
|
||||||
var result = certGenerator.generateCertsForHostname(hostname, {
|
|
||||||
cert: cache_rootCACrtFileContent,
|
|
||||||
key: cache_rootCAKeyFileContent
|
|
||||||
});
|
|
||||||
fs.writeFileSync(keyFile, result.privateKey);
|
|
||||||
fs.writeFileSync(crtFile, result.certificate);
|
|
||||||
callback(null, result.privateKey, result.certificate);
|
|
||||||
|
|
||||||
}catch(e){
|
|
||||||
callback(e);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
callback(null , fs.readFileSync(keyFile) , fs.readFileSync(crtFile));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},function(err,keyContent,crtContent){
|
cb(error, keyContent, crtContent);
|
||||||
if(!err){
|
|
||||||
certCallback(null ,keyContent,crtContent);
|
|
||||||
}else{
|
|
||||||
certCallback(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function createCert(hostname,callback){
|
// set default common name of the cert
|
||||||
checkRootCA();
|
crtMgr.generateRootCA = function (cb) {
|
||||||
|
doGenerate(false);
|
||||||
|
|
||||||
var cmd = cmd_genCert + " __host __path".replace(/__host/,hostname).replace(/__path/,certDir);
|
function doGenerate(overwrite) {
|
||||||
exec(cmd,{ cwd : certDir },function(err,stdout,stderr){
|
const rootOptions = {
|
||||||
if(err){
|
commonName: 'AnyProxy',
|
||||||
callback && callback(new Error("error when generating certificate"),null);
|
overwrite: !!overwrite
|
||||||
}else{
|
};
|
||||||
var tipText = "certificate created for __HOST".replace(/__HOST/,hostname);
|
|
||||||
logUtil.printLog(color.yellow(color.bold("[internal https]")) + color.yellow(tipText)) ;
|
|
||||||
callback(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearCerts(cb){
|
easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
|
||||||
if(isWin){
|
if (!error) {
|
||||||
exec("del * /q",{cwd : certDir},cb);
|
const certDir = path.dirname(keyPath);
|
||||||
}else{
|
logUtil.printLog(color.cyan('The cert is generated at "' + certDir + '"'));
|
||||||
exec("rm *.key *.csr *.crt *.srl",{cwd : certDir},cb);
|
if(isWin){
|
||||||
}
|
exec("start .",{ cwd : certDir });
|
||||||
}
|
}else{
|
||||||
|
exec("open .",{ cwd : certDir });
|
||||||
function isRootCAFileExists(){
|
}
|
||||||
return (fs.existsSync(rootCAcrtFilePath) && fs.existsSync(rootCAkeyFilePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootCAExists = false;
|
|
||||||
function checkRootCA(){
|
|
||||||
if(rootCAExists) return;
|
|
||||||
if(!isRootCAFileExists()){
|
|
||||||
logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR);
|
|
||||||
logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR);
|
|
||||||
logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR);
|
|
||||||
logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR);
|
|
||||||
logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR);
|
|
||||||
process.exit(0);
|
|
||||||
} else{
|
|
||||||
rootCAExists = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateRootCA(){
|
|
||||||
|
|
||||||
if(isRootCAFileExists()){
|
|
||||||
logUtil.printLog(color.yellow("rootCA exists at " + certDir));
|
|
||||||
var rl = readline.createInterface({
|
|
||||||
input : process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
rl.question("do you really want to generate a new one ?)(yes/NO)", function(answer) {
|
|
||||||
if(/yes/i.test(answer)){
|
|
||||||
startGenerating();
|
|
||||||
}else{
|
|
||||||
logUtil.printLog("will not generate a new one");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.close();
|
if (error === 'ROOT_CA_EXISTED') {
|
||||||
});
|
var rl = readline.createInterface({
|
||||||
}else{
|
input : process.stdin,
|
||||||
startGenerating();
|
output: process.stdout
|
||||||
}
|
});
|
||||||
|
|
||||||
function startGenerating(){
|
rl.question("do you really want to generate a new one ?)(yes/NO)", function(answer) {
|
||||||
//clear old certs
|
if(/yes/i.test(answer)){
|
||||||
clearCerts(function(){
|
doGenerate(true);
|
||||||
logUtil.printLog(color.green("temp certs cleared"));
|
}else{
|
||||||
try{
|
console.log("will not generate a new one");
|
||||||
var result = certGenerator.generateRootCA();
|
|
||||||
fs.writeFileSync(rootCAkeyFilePath, result.privateKey);
|
|
||||||
fs.writeFileSync(rootCAcrtFilePath, result.certificate);
|
|
||||||
|
|
||||||
logUtil.printLog(color.green("rootCA generated"));
|
}
|
||||||
logUtil.printLog(color.green(color.bold("please trust the rootCA.crt in " + certDir)));
|
rl.close();
|
||||||
logUtil.printLog(color.green(color.bold("or you may get it via anyproxy webinterface")));
|
});
|
||||||
|
|
||||||
if(isWin){
|
return;
|
||||||
exec("start .",{cwd : certDir});
|
|
||||||
}else{
|
|
||||||
exec("open .",{cwd : certDir});
|
|
||||||
}
|
|
||||||
|
|
||||||
}catch(e){
|
|
||||||
logUtil.printLog(color.red(e));
|
|
||||||
logUtil.printLog(color.red(e.stack));
|
|
||||||
logUtil.printLog(color.red("fail to generate root CA"),logUtil.T_ERR);
|
|
||||||
}
|
}
|
||||||
|
cb(error, keyPath, crtPath);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function getRootCAFilePath(){
|
};
|
||||||
if(isRootCAFileExists()){
|
|
||||||
return rootCAcrtFilePath;
|
|
||||||
}else{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.getRootCAFilePath = getRootCAFilePath;
|
module.exports = crtMgr;
|
||||||
module.exports.generateRootCA = generateRootCA;
|
|
||||||
module.exports.getCertificate = getCertificate;
|
|
||||||
module.exports.createCert = createCert;
|
|
||||||
module.exports.clearCerts = clearCerts;
|
|
||||||
module.exports.isRootCAFileExists = isRootCAFileExists;
|
|
@ -14,8 +14,7 @@ var http = require("http"),
|
|||||||
logUtil = require("./log"),
|
logUtil = require("./log"),
|
||||||
httpsServerMgr = require("./httpsServerMgr");
|
httpsServerMgr = require("./httpsServerMgr");
|
||||||
|
|
||||||
var defaultRule = require("./rule_default.js"),
|
var userRule = util.freshRequire('./rule_default');
|
||||||
userRule = defaultRule; //init
|
|
||||||
|
|
||||||
function userRequestHandler(req,userRes){
|
function userRequestHandler(req,userRes){
|
||||||
/*
|
/*
|
||||||
@ -140,6 +139,8 @@ function userRequestHandler(req,userRes){
|
|||||||
options.headers = util.lower_keys(options.headers);
|
options.headers = util.lower_keys(options.headers);
|
||||||
options.headers["content-length"] = reqData.length; //rewrite content length info
|
options.headers["content-length"] = reqData.length; //rewrite content length info
|
||||||
|
|
||||||
|
options.headers = util.upper_keys(options.headers);
|
||||||
|
|
||||||
//send request
|
//send request
|
||||||
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
|
var proxyReq = ( /https/.test(protocol) ? https : http).request(options, function(res) {
|
||||||
|
|
||||||
@ -202,7 +203,7 @@ function userRequestHandler(req,userRes){
|
|||||||
|
|
||||||
//delay
|
//delay
|
||||||
},function(callback){
|
},function(callback){
|
||||||
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
|
var pauseTimeInMS = userRule.pauseBeforeSendingResponse(req,res);
|
||||||
if(pauseTimeInMS){
|
if(pauseTimeInMS){
|
||||||
setTimeout(callback,pauseTimeInMS);
|
setTimeout(callback,pauseTimeInMS);
|
||||||
}else{
|
}else{
|
||||||
@ -385,9 +386,13 @@ function connectReqHandler(req, socket, head){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return return the merged rule for reference
|
||||||
|
*/
|
||||||
function setRules(newRule){
|
function setRules(newRule){
|
||||||
|
|
||||||
if(!newRule){
|
if(!newRule){
|
||||||
return;
|
return userRule;
|
||||||
}else{
|
}else{
|
||||||
|
|
||||||
if(!newRule.summary){
|
if(!newRule.summary){
|
||||||
@ -396,7 +401,7 @@ function setRules(newRule){
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
userRule = util.merge(defaultRule,newRule);
|
userRule = util.merge(userRule,newRule);
|
||||||
|
|
||||||
var functions = [];
|
var functions = [];
|
||||||
if('function' == typeof(userRule.init)){
|
if('function' == typeof(userRule.init)){
|
||||||
@ -416,6 +421,7 @@ function setRules(newRule){
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return userRule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,3 +433,4 @@ module.exports.userRequestHandler = userRequestHandler;
|
|||||||
module.exports.connectReqHandler = connectReqHandler;
|
module.exports.connectReqHandler = connectReqHandler;
|
||||||
module.exports.setRules = setRules;
|
module.exports.setRules = setRules;
|
||||||
module.exports.getRuleSummary = getRuleSummary;
|
module.exports.getRuleSummary = getRuleSummary;
|
||||||
|
module.exports.token = Date.now();
|
||||||
|
@ -73,6 +73,7 @@ setTimeout(function(){
|
|||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
token: Date.now(),
|
||||||
summary:function(){
|
summary:function(){
|
||||||
var tip = "the default rule for AnyProxy.";
|
var tip = "the default rule for AnyProxy.";
|
||||||
if(!isRootCAFileExists){
|
if(!isRootCAFileExists){
|
||||||
@ -102,7 +103,10 @@ module.exports = {
|
|||||||
if(err){
|
if(err){
|
||||||
callback(200, {}, "[AnyProxy failed to load local file] " + err);
|
callback(200, {}, "[AnyProxy failed to load local file] " + err);
|
||||||
}else{
|
}else{
|
||||||
callback(200, {}, buffer);
|
var header = {
|
||||||
|
'Content-Type': utils.contentType(req.anyproxy_map_local)
|
||||||
|
};
|
||||||
|
callback(200, header, buffer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
60
lib/util.js
60
lib/util.js
@ -1,8 +1,11 @@
|
|||||||
var fs = require("fs"),
|
var fs = require("fs"),
|
||||||
path = require("path"),
|
path = require("path"),
|
||||||
|
mime = require('mime-types'),
|
||||||
|
color = require('colorful'),
|
||||||
|
logUtil = require("./log"),
|
||||||
exec = require('child_process').exec;
|
exec = require('child_process').exec;
|
||||||
|
|
||||||
|
const changeCase = require('change-case');
|
||||||
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"}
|
||||||
module.exports.lower_keys = function(obj){
|
module.exports.lower_keys = function(obj){
|
||||||
for(var key in obj){
|
for(var key in obj){
|
||||||
@ -34,7 +37,7 @@ module.exports.getAnyProxyHome = function(){
|
|||||||
|
|
||||||
if(!fs.existsSync(home)){
|
if(!fs.existsSync(home)){
|
||||||
try{
|
try{
|
||||||
fs.mkdirSync(home,0777);
|
fs.mkdirSync(home, '0777');
|
||||||
}catch(e){
|
}catch(e){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -48,7 +51,7 @@ module.exports.generateCacheDir = function(){
|
|||||||
var rand = Math.floor(Math.random() * 1000000),
|
var rand = Math.floor(Math.random() * 1000000),
|
||||||
cachePath = path.join(util.getAnyProxyHome(),"./" + CACHE_DIR_PREFIX + rand);
|
cachePath = path.join(util.getAnyProxyHome(),"./" + CACHE_DIR_PREFIX + rand);
|
||||||
|
|
||||||
fs.mkdirSync(cachePath,0777);
|
fs.mkdirSync(cachePath, '0777');
|
||||||
return cachePath;
|
return cachePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,4 +105,53 @@ module.exports.filewalker = function(root,cb){
|
|||||||
|
|
||||||
cb && cb.apply(null,[null,ret]);
|
cb && cb.apply(null,[null,ret]);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 获取文件所对应的content-type以及content-length等信息
|
||||||
|
* 比如在useLocalResponse的时候会使用到
|
||||||
|
*/
|
||||||
|
module.exports.contentType = function (filepath) {
|
||||||
|
return mime.contentType(path.extname(filepath));
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 读取file的大小,以byte为单位
|
||||||
|
*/
|
||||||
|
module.exports.contentLength = function (filepath) {
|
||||||
|
try {
|
||||||
|
var stat = fs.statSync(filepath);
|
||||||
|
return stat.size;
|
||||||
|
} catch (e) {
|
||||||
|
logUtil.printLog(color.red("\nfailed to ready local file : " + filepath));
|
||||||
|
logUtil.printLog(color.red(e));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.showRootInstallTip = function () {
|
||||||
|
logUtil.printLog(color.red("can not find rootCA.crt or rootCA.key"), logUtil.T_ERR);
|
||||||
|
logUtil.printLog(color.red("you may generate one by the following methods"), logUtil.T_ERR);
|
||||||
|
logUtil.printLog(color.red("\twhen using globally : anyproxy --root"), logUtil.T_ERR);
|
||||||
|
logUtil.printLog(color.red("\twhen using as a module : require(\"anyproxy\").generateRootCA();"), logUtil.T_ERR);
|
||||||
|
logUtil.printLog(color.red("\tmore info : https://github.com/alibaba/anyproxy/wiki/How-to-config-https-proxy"), logUtil.T_ERR);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* remove the cache before requering, the path SHOULD BE RELATIVE TO UTIL.JS
|
||||||
|
*/
|
||||||
|
module.exports.freshRequire = function (path) {
|
||||||
|
delete require.cache[require.resolve(path)];
|
||||||
|
return require(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.upper_keys = function (obj) {
|
||||||
|
var upperObject = {};
|
||||||
|
for(var key in obj) {
|
||||||
|
var upperKey = changeCase.headerCase(key);
|
||||||
|
upperObject[upperKey] = obj[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return upperObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
23
package.json
23
package.json
@ -10,6 +10,7 @@
|
|||||||
"async": "~0.9.0",
|
"async": "~0.9.0",
|
||||||
"async-task-mgr": ">=1.1.0",
|
"async-task-mgr": ">=1.1.0",
|
||||||
"body-parser": "^1.13.1",
|
"body-parser": "^1.13.1",
|
||||||
|
"change-case": "^3.0.0",
|
||||||
"colorful": "^2.1.0",
|
"colorful": "^2.1.0",
|
||||||
"commander": "~2.3.0",
|
"commander": "~2.3.0",
|
||||||
"compression": "^1.4.4",
|
"compression": "^1.4.4",
|
||||||
@ -17,19 +18,35 @@
|
|||||||
"iconv-lite": "^0.4.6",
|
"iconv-lite": "^0.4.6",
|
||||||
"ip": "^0.3.2",
|
"ip": "^0.3.2",
|
||||||
"juicer": "^0.6.6-stable",
|
"juicer": "^0.6.6-stable",
|
||||||
|
"mime-types": "2.1.11",
|
||||||
"nedb": "^0.11.0",
|
"nedb": "^0.11.0",
|
||||||
|
"node-easy-cert": "^1.0.0",
|
||||||
"node-forge": "^0.6.39",
|
"node-forge": "^0.6.39",
|
||||||
"npm": "^2.7.0",
|
"npm": "^2.7.0",
|
||||||
"promise": "^7.0.4",
|
"promise": "^7.0.4",
|
||||||
"qrcode-npm": "0.0.3",
|
"qrcode-npm": "0.0.3",
|
||||||
"stream-throttle": "^0.1.3",
|
"stream-throttle": "^0.1.3",
|
||||||
"ws": "^0.4.32"
|
"ws": "^1.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"proxy-eval": ">=1.1.2"
|
"babel-polyfill": "^6.13.0",
|
||||||
|
"babel-preset-es2015": "^6.13.2",
|
||||||
|
"babel-preset-stage-0": "^6.5.0",
|
||||||
|
"babel-register": "^6.11.6",
|
||||||
|
"https-proxy-agent": "^1.0.0",
|
||||||
|
"jasmine": "^2.4.1",
|
||||||
|
"koa": "^1.2.1",
|
||||||
|
"koa-body": "^1.4.0",
|
||||||
|
"koa-router": "^5.4.0",
|
||||||
|
"koa-send": "^3.2.0",
|
||||||
|
"koa-websocket": "^2.0.0",
|
||||||
|
"nodeunit": "^0.9.1",
|
||||||
|
"request": "^2.74.0",
|
||||||
|
"stream-equal": "0.1.8"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "sh test.sh"
|
"test": "sh test/test.sh",
|
||||||
|
"testserver": "node test/server/startServer.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {},
|
"optionalDependencies": {},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
27
proxy.js
27
proxy.js
@ -11,7 +11,6 @@ var http = require('http'),
|
|||||||
color = require('colorful'),
|
color = require('colorful'),
|
||||||
certMgr = require("./lib/certMgr"),
|
certMgr = require("./lib/certMgr"),
|
||||||
getPort = require("./lib/getPort"),
|
getPort = require("./lib/getPort"),
|
||||||
requestHandler = require("./lib/requestHandler"),
|
|
||||||
Recorder = require("./lib/recorder"),
|
Recorder = require("./lib/recorder"),
|
||||||
logUtil = require("./lib/log"),
|
logUtil = require("./lib/log"),
|
||||||
wsServer = require("./lib/wsServer"),
|
wsServer = require("./lib/wsServer"),
|
||||||
@ -37,7 +36,8 @@ var T_TYPE_HTTP = 0,
|
|||||||
DEFAULT_HOST = "localhost",
|
DEFAULT_HOST = "localhost",
|
||||||
DEFAULT_TYPE = T_TYPE_HTTP;
|
DEFAULT_TYPE = T_TYPE_HTTP;
|
||||||
|
|
||||||
var default_rule = require('./lib/rule_default');
|
var default_rule = util.freshRequire('./rule_default');
|
||||||
|
var requestHandler = util.freshRequire('./requestHandler');
|
||||||
|
|
||||||
//option
|
//option
|
||||||
//option.type : 'http'(default) or 'https'
|
//option.type : 'http'(default) or 'https'
|
||||||
@ -70,22 +70,39 @@ function proxyServer(option){
|
|||||||
logUtil.setPrintStatus(false);
|
logUtil.setPrintStatus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy the rule to keep the original proxyRules independent
|
||||||
|
proxyRules = Object.assign({}, proxyRules);
|
||||||
|
|
||||||
|
var currentRule = requestHandler.setRules(proxyRules); //TODO : optimize calling for set rule
|
||||||
|
|
||||||
if(!!option.interceptHttps){
|
if(!!option.interceptHttps){
|
||||||
default_rule.setInterceptFlag(true);
|
if (!certMgr.isRootCAFileExists()) {
|
||||||
|
util.showRootInstallTip();
|
||||||
|
process.exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRule.setInterceptFlag(true);
|
||||||
|
|
||||||
//print a tip when using https features in Node < v0.12
|
//print a tip when using https features in Node < v0.12
|
||||||
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
var nodeVersion = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
|
||||||
if(nodeVersion < 0.12){
|
if(nodeVersion < 0.12){
|
||||||
logUtil.printLog(color.red("node >= v0.12 is required when trying to intercept HTTPS requests :("), logUtil.T_ERR);
|
logUtil.printLog(color.red("node >= v0.12 is required when trying to intercept HTTPS requests :("), logUtil.T_ERR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logUtil.printLog(color.blue("The WebSocket will not work properly in the https intercept mode :("), logUtil.T_TIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(option.throttle){
|
if(option.throttle){
|
||||||
logUtil.printLog("throttle :" + option.throttle + "kb/s");
|
logUtil.printLog("throttle :" + option.throttle + "kb/s");
|
||||||
GLOBAL._throttle = new ThrottleGroup({rate: 1024 * parseInt(option.throttle) }); // rate - byte/sec
|
const rate = parseInt(option.throttle);
|
||||||
|
if (rate < 1) {
|
||||||
|
logUtil.printLog(color.red('Invalid throttle rate value, should be positive integer\n'), logUtil.T_ERR);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
GLOBAL._throttle = new ThrottleGroup({rate: 1024 * parseFloat(option.throttle) }); // rate - byte/sec
|
||||||
}
|
}
|
||||||
|
|
||||||
requestHandler.setRules(proxyRules); //TODO : optimize calling for set rule
|
|
||||||
self.httpProxyServer = null;
|
self.httpProxyServer = null;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
|
28
test/data/headers.js
Normal file
28
test/data/headers.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* 用于放置所有header信息的测试数据
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get 和 Post共有的header信息
|
||||||
|
/*eslint max-len: ["off"]*/
|
||||||
|
const CommonRequestHeader = {
|
||||||
|
Accept: 'application/json;charset=utf-8,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
|
||||||
|
'Accept-Charset': 'utf-8',
|
||||||
|
'Accept-Encoding': 'gzip, deflate',
|
||||||
|
'Accept-Language': 'zh-CN',
|
||||||
|
'Accept-Datetime': 'Thu, 31 May 2007 20:35:00 GMT',
|
||||||
|
'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'Cookie': 'testCookie1=cookie1; testCookie2=cookie2',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
'Date': 'Tue, 15 Nov 1994 08:12:31 GMT',
|
||||||
|
'Origin': 'http://localhost',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
CommonRequestHeader
|
||||||
|
};
|
||||||
|
|
3
test/data/test.css
Normal file
3
test/data/test.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.test {
|
||||||
|
display: block;
|
||||||
|
}
|
BIN
test/data/test.eot
Executable file
BIN
test/data/test.eot
Executable file
Binary file not shown.
3
test/data/test.js
Normal file
3
test/data/test.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function test () {
|
||||||
|
console.info('This is nothing but a js file, to test the js download');
|
||||||
|
}
|
3
test/data/test.json
Normal file
3
test/data/test.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
'testkey': 'this is just a normal json file'
|
||||||
|
}
|
BIN
test/data/test.png
Executable file
BIN
test/data/test.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
14
test/data/test.svg
Executable file
14
test/data/test.svg
Executable file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata>
|
||||||
|
<defs>
|
||||||
|
<font id="fontello" horiz-adv-x="1000" >
|
||||||
|
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||||
|
<missing-glyph horiz-adv-x="1000" />
|
||||||
|
<glyph glyph-name="glass" unicode="" d="M948 746q0-19-24-43l-353-353v-429h179q15 0 25-10t11-25-11-25-25-11h-500q-14 0-25 11t-11 25 11 25 25 10h179v429l-353 353q-24 24-24 43 0 13 10 21t21 9 24 3h786q13 0 24-3t21-9 10-21z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
|
<glyph glyph-name="music" unicode="" d="M857 725v-625q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 33 58 18 54 6q58 0 107-22v300l-429-132v-396q0-28-19-50t-48-33-58-18-53-6-54 6-58 18-48 33-19 50 19 50 48 34 58 17 54 6q58 0 107-21v539q0 17 10 32t28 20l464 142q7 3 16 3 22 0 38-16t15-38z" horiz-adv-x="857.1" />
|
||||||
|
</font>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
BIN
test/data/test.ttf
Executable file
BIN
test/data/test.ttf
Executable file
Binary file not shown.
BIN
test/data/test.webp
Normal file
BIN
test/data/test.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
BIN
test/data/test.woff
Executable file
BIN
test/data/test.woff
Executable file
Binary file not shown.
BIN
test/data/test.woff2
Executable file
BIN
test/data/test.woff2
Executable file
Binary file not shown.
459
test/no_rule_spec.js
Normal file
459
test/no_rule_spec.js
Normal file
@ -0,0 +1,459 @@
|
|||||||
|
const http = require('http');
|
||||||
|
const querystring = require('querystring');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const Buffer = require('buffer').Buffer;
|
||||||
|
const Server = require('./server/server.js');
|
||||||
|
const
|
||||||
|
{
|
||||||
|
proxyGet,
|
||||||
|
proxyPost,
|
||||||
|
directGet,
|
||||||
|
directPost,
|
||||||
|
directUpload,
|
||||||
|
proxyUpload,
|
||||||
|
generateUrl,
|
||||||
|
proxyPut,
|
||||||
|
directPut,
|
||||||
|
proxyDelete,
|
||||||
|
directDelete,
|
||||||
|
directHead,
|
||||||
|
proxyHead,
|
||||||
|
directOptions,
|
||||||
|
proxyOptions,
|
||||||
|
proxyPutUpload,
|
||||||
|
directPutUpload
|
||||||
|
} = require('./util/HttpUtil.js');
|
||||||
|
const { CommonRequestHeader } = require('./data/headers.js');
|
||||||
|
const { isCommonResHeaderEqual, isCommonReqEqual, printLog } = require('./util/CommonUtil.js');
|
||||||
|
const color = require('colorful');
|
||||||
|
const streamEqual = require('stream-equal');
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
|
const ProxyServerUtil = require('./util/ProxyServerUtil.js');
|
||||||
|
|
||||||
|
const wsHost = 'ws://localhost:3000/test/socket';
|
||||||
|
|
||||||
|
testRequest('http');
|
||||||
|
testRequest('https');
|
||||||
|
|
||||||
|
// Test suites for http and https request
|
||||||
|
function testRequest(protocol = 'http') {
|
||||||
|
|
||||||
|
function constructUrl(urlPath) {
|
||||||
|
return generateUrl(protocol, urlPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Test request without proxy rules in protocol ' + protocol, () => {
|
||||||
|
let proxyServer ;
|
||||||
|
let serverInstance;
|
||||||
|
|
||||||
|
beforeAll((done) => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||||
|
printLog('Start server for no_rule_spec');
|
||||||
|
|
||||||
|
serverInstance = new Server();
|
||||||
|
proxyServer = ProxyServerUtil.defaultProxyServer();
|
||||||
|
setTimeout(function() {
|
||||||
|
done();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
serverInstance && serverInstance.close();
|
||||||
|
proxyServer && proxyServer.close();
|
||||||
|
printLog('Closed server for no_rule_spec');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Get should work as direct without proxy rules', (done) => {
|
||||||
|
const url = constructUrl('/test');
|
||||||
|
const getParam = {
|
||||||
|
param: 'nothing'
|
||||||
|
};
|
||||||
|
|
||||||
|
proxyGet(url, getParam, CommonRequestHeader).then((proxyRes) => {
|
||||||
|
directGet(url, getParam, CommonRequestHeader).then(directRes => {
|
||||||
|
|
||||||
|
expect(proxyRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error happend in direct get:', error);
|
||||||
|
done.fail('error happend in direct get');
|
||||||
|
});
|
||||||
|
|
||||||
|
}, error => {
|
||||||
|
console.log('error happened in proxy get:', error);
|
||||||
|
done.fail('error happend in proxy get');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Post should work as direct without proxy rules', (done) => {
|
||||||
|
const url = constructUrl('/test/getuser');
|
||||||
|
const param = {
|
||||||
|
param: 'postnothing'
|
||||||
|
};
|
||||||
|
|
||||||
|
proxyPost(url, param, CommonRequestHeader).then(proxyRes => {
|
||||||
|
directPost(url, param, CommonRequestHeader).then(directRes => {
|
||||||
|
|
||||||
|
expect(proxyRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error in direct post:', error);
|
||||||
|
done.fail('error happend in direct post');
|
||||||
|
});
|
||||||
|
|
||||||
|
}, error => {
|
||||||
|
console.log('error happened in proxy post,', error);
|
||||||
|
done.fail('error happend in proxy post');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PUT should work as direct without proxy rules', done => {
|
||||||
|
const url = constructUrl('/test/put');
|
||||||
|
const param = {
|
||||||
|
param: 'putsomething'
|
||||||
|
};
|
||||||
|
proxyPut(url, param, CommonRequestHeader).then(proxyRes => {
|
||||||
|
directPut(url, param, CommonRequestHeader).then(directRes => {
|
||||||
|
expect(directRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in direct put', error);
|
||||||
|
done.fail('error happened in direct put');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in proxy put', error);
|
||||||
|
done.fail('error happened in proxy put');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DELETE rquest should work as direct without proxy rules', (done) => {
|
||||||
|
const url = constructUrl('/test/delete/123456');
|
||||||
|
|
||||||
|
proxyDelete(url, {}, CommonRequestHeader).then(proxyRes => {
|
||||||
|
directDelete(url, {}, CommonRequestHeader).then(directRes => {
|
||||||
|
expect(directRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in direct delete :', error);
|
||||||
|
done.fail('error happened in direct delete');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in proxy delete :', error);
|
||||||
|
done.fail('error happened in proxy delete');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('HEAD request should work as direct without proxy rules', (done) => {
|
||||||
|
const url = constructUrl('/test/head');
|
||||||
|
|
||||||
|
proxyHead(url, CommonRequestHeader)
|
||||||
|
.then(proxyRes => {
|
||||||
|
directHead(url, CommonRequestHeader)
|
||||||
|
.then(directRes => {
|
||||||
|
expect(directRes.statusCode).toEqual(200);
|
||||||
|
expect(directRes.body).toEqual('');
|
||||||
|
|
||||||
|
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in direct head request:', error);
|
||||||
|
done.fail('error happened in direct head request');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in proxy head request:', error);
|
||||||
|
done.fail('error happened in proxy head request');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('OPTIONS request should work as direct without proxy rules', (done) => {
|
||||||
|
const url = constructUrl('/test/options');
|
||||||
|
|
||||||
|
proxyOptions(url, CommonRequestHeader)
|
||||||
|
.then(proxyRes => {
|
||||||
|
directOptions(url, CommonRequestHeader)
|
||||||
|
.then(directRes => {
|
||||||
|
expect(directRes.statusCode).toEqual(200);
|
||||||
|
expect(directRes.body).toEqual('could_be_empty');
|
||||||
|
|
||||||
|
expect(directRes.statusCode).toEqual(proxyRes.statusCode);
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(directRes.body).toEqual(proxyRes.body);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in direct options request:', error);
|
||||||
|
done.fail('error happened in direct options request');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error happened in proxy options request:', error);
|
||||||
|
done.fail('error happened in proxy options request');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Response code should be honored as direct without proxy rules', () => {
|
||||||
|
[301, 302, 303].forEach(code => {
|
||||||
|
testRedirect(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
function testRedirect (redirectCode) {
|
||||||
|
it(`${redirectCode} response should work as direct without proxy rules`, (done) => {
|
||||||
|
const url = constructUrl(`/test/response/${redirectCode}`);
|
||||||
|
|
||||||
|
proxyGet(url)
|
||||||
|
.then(proxyRes => {
|
||||||
|
directGet(url)
|
||||||
|
.then(directRes => {
|
||||||
|
const redirects = directRes.request._redirect.redirects || [];
|
||||||
|
const proxyRedirects = proxyRes.request._redirect.redirects || [];
|
||||||
|
expect(redirects.length).toEqual(1);
|
||||||
|
expect(proxyRedirects.length).toEqual(1);
|
||||||
|
|
||||||
|
expect(redirects[0].statusCode).toEqual(redirectCode);
|
||||||
|
expect(redirects[0].redirectUri).toEqual(proxyRedirects[0].redirectUri);
|
||||||
|
expect(redirects[0].statusCode).toEqual(proxyRedirects[0].statusCode);
|
||||||
|
if (protocol === 'https') {
|
||||||
|
expect(redirects[0].redirectUri).toEqual('https://localhost:3001/test');
|
||||||
|
} else {
|
||||||
|
expect(redirects[0].redirectUri).toEqual('http://localhost:3000/test');
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.log(`error happened in direct ${redirectCode}:`, error);
|
||||||
|
done.fail(`error happened in direct ${redirectCode}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
}, error => {
|
||||||
|
console.log(`error happened in proxy ${redirectCode}:`, error);
|
||||||
|
done.fail(`error happened in proxy ${redirectCode}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
describe('Test file download ', () => {
|
||||||
|
const testArray = [
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/png'),
|
||||||
|
type: 'png',
|
||||||
|
contentType: 'image/png'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/webp'),
|
||||||
|
type: 'WEBP',
|
||||||
|
contentType: 'image/webp'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/json'),
|
||||||
|
type: 'JSON',
|
||||||
|
contentType: 'application/json; charset=utf-8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/css'),
|
||||||
|
type: 'CSS',
|
||||||
|
contentType: 'text/css; charset=utf-8'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/ttf'),
|
||||||
|
type: 'TTF',
|
||||||
|
contentType: 'application/x-font-ttf'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/eot'),
|
||||||
|
type: 'EOT',
|
||||||
|
contentType: 'application/vnd.ms-fontobject'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/svg'),
|
||||||
|
type: 'SVG',
|
||||||
|
contentType: 'image/svg+xml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/woff'),
|
||||||
|
type: 'WOFF',
|
||||||
|
contentType: 'application/font-woff'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: constructUrl('/test/download/woff2'),
|
||||||
|
type: 'WOFF2',
|
||||||
|
contentType: 'application/font-woff2'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
testArray.forEach(item => {
|
||||||
|
testFileDownload(item.url, item.type, item.contentType);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 封装测试文件下载的测试工具类
|
||||||
|
function testFileDownload (url, filetype, contentType) {
|
||||||
|
const describe = `${filetype} file download without rules should be work as direct download`;
|
||||||
|
const param = {};
|
||||||
|
|
||||||
|
it(describe, (done) => {
|
||||||
|
|
||||||
|
proxyGet(url, param).then(proxyRes => {
|
||||||
|
directGet(url, param).then(directRes => {
|
||||||
|
expect(proxyRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||||
|
expect(proxyRes.body).toEqual(directRes.body);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error in direct get :', filetype, error);
|
||||||
|
done.fail(`error happend in direct get ${filetype}`);
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error in proxy get :', filetype, error);
|
||||||
|
done.fail(`error happend in proxy get ${filetype}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test file upload', () => {
|
||||||
|
const formParams = {
|
||||||
|
param1: 'param_1',
|
||||||
|
param2: 'param2'
|
||||||
|
};
|
||||||
|
it('POST upload should be working', (done) => {
|
||||||
|
const url = constructUrl('/test/upload/png');
|
||||||
|
const filePath = path.resolve('./test/data/test.png');
|
||||||
|
|
||||||
|
proxyUpload(url, filePath, formParams)
|
||||||
|
.then(proxyRes => {
|
||||||
|
directUpload(url, filePath, formParams)
|
||||||
|
.then((directRes) => {
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
|
||||||
|
assertReponse(proxyRes, directRes, filePath, done);
|
||||||
|
}, error => {
|
||||||
|
console.error('error in direct upload:', error);
|
||||||
|
done.fail('error in direct upload');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error in proxy upload:', error);
|
||||||
|
done.fail('error in proxy upload:');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PUT upload should be working', (done) => {
|
||||||
|
const url = constructUrl('/test/upload/putpng');
|
||||||
|
const filePath = path.resolve('./test/data/test.png');
|
||||||
|
proxyPutUpload(url, filePath, formParams)
|
||||||
|
.then(proxyRes => {
|
||||||
|
directPutUpload(url, filePath, formParams)
|
||||||
|
.then((directRes) => {
|
||||||
|
expect(isCommonResHeaderEqual(directRes.headers, proxyRes.headers, url)).toBe(true);
|
||||||
|
|
||||||
|
assertReponse(proxyRes, directRes, filePath, done);
|
||||||
|
}, error => {
|
||||||
|
console.error('error in direct upload:', error);
|
||||||
|
done.fail('error in direct upload');
|
||||||
|
});
|
||||||
|
}, error => {
|
||||||
|
console.error('error in proxy upload:', error);
|
||||||
|
done.fail('error in proxy upload:');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function assertReponse (proxyRes, directRes, originFilePath, done) {
|
||||||
|
expect(proxyRes.statusCode).toEqual(200);
|
||||||
|
|
||||||
|
expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||||
|
// expect(proxyRes.headers.reqbody).toEqual(directRes.headers.reqbody);
|
||||||
|
|
||||||
|
// the body will be the file path
|
||||||
|
const directUploadedStream = fs.createReadStream(directRes.body);
|
||||||
|
const proxyUploadedStream = fs.createReadStream(proxyRes.body);
|
||||||
|
const localFileStream = fs.createReadStream(originFilePath);
|
||||||
|
streamEqual(directUploadedStream, localFileStream)
|
||||||
|
.then(isLocalEqual => {
|
||||||
|
expect(isLocalEqual).toBe(true);
|
||||||
|
streamEqual(directUploadedStream, proxyUploadedStream)
|
||||||
|
.then(isUploadedEqual => {
|
||||||
|
expect(isUploadedEqual).toBe(true);
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error in comparing directUpload with proxy:\n',error);
|
||||||
|
done.fail('error in comparing directUpload with proxy');
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.error('error in comparing directUpload with local:\n',error);
|
||||||
|
done.fail('error in comparing directUpload with local');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// describe('Test Big file download', () => {
|
||||||
|
// // const url = '/test/download/bigfile';
|
||||||
|
// const url = 'http://yunpan.alibaba-inc.com/downloadService.do?token=pZWiXMXUguIUQDvR098qnUVqVAWhNVY6';
|
||||||
|
// const contentType = 'application/octet-stream';
|
||||||
|
// const param = {};
|
||||||
|
// it('BIG file downlaod should be working', (done) => {
|
||||||
|
// directGet(url, param, CommonRequestHeader).then(proxyRes => {
|
||||||
|
// console.info('proxyRes body:', proxyRes.body);
|
||||||
|
|
||||||
|
// directGet(url, param, CommonRequestHeader).then(directRes => {
|
||||||
|
// expect(proxyRes.statusCode).toEqual(200);
|
||||||
|
// expect(proxyRes.headers['content-type']).toEqual(contentType);
|
||||||
|
|
||||||
|
// expect(proxyRes.statusCode).toEqual(directRes.statusCode);
|
||||||
|
// expect(proxyRes.headers['content-type']).toEqual(directRes.headers['content-type']);
|
||||||
|
// expect(proxyRes.body).toEqual(directRes.body);
|
||||||
|
// done();
|
||||||
|
// }, error => {
|
||||||
|
// console.error('error in direct get bigfile :', error);
|
||||||
|
// done.fail(`error happend in direct get bigfile`);
|
||||||
|
// });
|
||||||
|
// }, error => {
|
||||||
|
// console.error('error in proxy get bigfile :', error);
|
||||||
|
// done.fail(`error happend in proxy get bigfile`);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
});
|
||||||
|
}
|
144
test/no_rule_websocket_spec.js
Normal file
144
test/no_rule_websocket_spec.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Test suites for WebSocket.
|
||||||
|
* ONLY TO ENSURE THE REQUEST WILL BE BYPASSED SUCCESSFULLY, WE HAVEN'T SUPPORTTED WEBSOCKET YET.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const ProxyServerUtil = require('./util/ProxyServerUtil.js');
|
||||||
|
const { generateWsUrl, directWs, proxyWs } = require('./util/HttpUtil.js');
|
||||||
|
const Server = require('./server/server.js');
|
||||||
|
const { printLog } = require('./util/CommonUtil.js');
|
||||||
|
|
||||||
|
const wsHost = 'ws://localhost:3000/test/socket';
|
||||||
|
|
||||||
|
testWebsocket('ws');
|
||||||
|
testWebsocket('wss');
|
||||||
|
|
||||||
|
function testWebsocket(protocol) {
|
||||||
|
describe('Test WebSocket in protocol : ' + protocol , () =>{
|
||||||
|
const url = generateWsUrl(protocol, '/test/socket');
|
||||||
|
let serverInstance ;
|
||||||
|
let proxyServer ;
|
||||||
|
|
||||||
|
beforeAll((done) => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
|
||||||
|
printLog('Start server for no_rule_websocket_spec');
|
||||||
|
serverInstance = new Server();
|
||||||
|
|
||||||
|
proxyServer = ProxyServerUtil.proxyServerWithoutHttpsIntercept();
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
done();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
serverInstance && serverInstance.close();
|
||||||
|
proxyServer && proxyServer.close();
|
||||||
|
printLog('Closed server for no_rule_websocket_spec');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Default websocket option', done => {
|
||||||
|
const sendMessage = 'Send the message with default option';
|
||||||
|
let directMessage ; // set the flag for direct message, compare when both direct and proxy got message
|
||||||
|
let proxyMessage;
|
||||||
|
|
||||||
|
const ws = directWs(url);
|
||||||
|
const porxyWsRef = proxyWs(url);
|
||||||
|
ws.on('open', () => {
|
||||||
|
ws.send(sendMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('open', () => {
|
||||||
|
porxyWsRef.send(sendMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', (data, flag) => {
|
||||||
|
const message = JSON.parse(data);
|
||||||
|
if (message.type === 'onMessage') {
|
||||||
|
directMessage = message.content;
|
||||||
|
compareMessageIfReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('message', (data, flag) => {
|
||||||
|
const message = JSON.parse(data);
|
||||||
|
if (message.type === 'onMessage') {
|
||||||
|
proxyMessage = message.content;
|
||||||
|
compareMessageIfReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', error => {
|
||||||
|
console.error('error happened in direct websocket:', error);
|
||||||
|
done.fail('Error happened in direct websocket');
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('error', error => {
|
||||||
|
console.error('error happened in proxy websocket:', error);
|
||||||
|
done.fail('Error happened in proxy websocket');
|
||||||
|
});
|
||||||
|
|
||||||
|
function compareMessageIfReady () {
|
||||||
|
if (directMessage && proxyMessage) {
|
||||||
|
expect(directMessage).toEqual(proxyMessage);
|
||||||
|
expect(directMessage).toEqual(sendMessage);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('masked:true', done => {
|
||||||
|
const sendMessage = 'Send the message with option masked:true';
|
||||||
|
let directMessage ; // set the flag for direct message, compare when both direct and proxy got message
|
||||||
|
let proxyMessage;
|
||||||
|
|
||||||
|
const ws = directWs(url);
|
||||||
|
const porxyWsRef = proxyWs(url);
|
||||||
|
ws.on('open', () => {
|
||||||
|
ws.send(sendMessage, { masked: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('open', () => {
|
||||||
|
porxyWsRef.send(sendMessage, { masked: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('message', (data, flag) => {
|
||||||
|
const message = JSON.parse(data);
|
||||||
|
if (message.type === 'onMessage') {
|
||||||
|
directMessage = message.content;
|
||||||
|
compareMessageIfReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('message', (data, flag) => {
|
||||||
|
const message = JSON.parse(data);
|
||||||
|
if (message.type === 'onMessage') {
|
||||||
|
proxyMessage = message.content;
|
||||||
|
compareMessageIfReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', error => {
|
||||||
|
console.error('error happened in direct websocket:', error);
|
||||||
|
done.fail('Error happened in direct websocket');
|
||||||
|
});
|
||||||
|
|
||||||
|
porxyWsRef.on('error', error => {
|
||||||
|
console.error('error happened in proxy websocket:', error);
|
||||||
|
|
||||||
|
done.fail('Error happened in proxy websocket');
|
||||||
|
});
|
||||||
|
|
||||||
|
function compareMessageIfReady () {
|
||||||
|
if (directMessage && proxyMessage) {
|
||||||
|
expect(directMessage).toEqual(proxyMessage);
|
||||||
|
expect(directMessage).toEqual(sendMessage);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
57
test/rule_shouldUseLocalResponse_spec.js
Normal file
57
test/rule_shouldUseLocalResponse_spec.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* test for rule shouldUseLocal
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ProxyServerUtil = require('./util/ProxyServerUtil.js');
|
||||||
|
const { proxyGet, generateUrl } = require('./util/HttpUtil.js');
|
||||||
|
const Server = require('./server/server.js');
|
||||||
|
const { printLog } = require('./util/CommonUtil.js');
|
||||||
|
|
||||||
|
const rule = require('./test_rules/shouldUseLocalResponseRule.js');
|
||||||
|
const expectedLocalBody = 'handled_in_local_response';
|
||||||
|
|
||||||
|
|
||||||
|
testWrapper('http');
|
||||||
|
testWrapper('https');
|
||||||
|
|
||||||
|
function testWrapper(protocol, ) {
|
||||||
|
describe('Rule shouldUseLocalResponse should be working in :' + protocol, () => {
|
||||||
|
let proxyServer ;
|
||||||
|
let serverInstance ;
|
||||||
|
|
||||||
|
beforeAll((done) => {
|
||||||
|
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000000;
|
||||||
|
printLog('Start server for rule_shouldUseLocalResponse_spec');
|
||||||
|
|
||||||
|
serverInstance = new Server();
|
||||||
|
|
||||||
|
proxyServer = ProxyServerUtil.proxyServerWithRule(rule);
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
done();
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
serverInstance && serverInstance.close();
|
||||||
|
proxyServer && proxyServer.close();
|
||||||
|
printLog('Close server for rule_shouldUseLocalResponse_spec');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should use local response if the assertion is true', done => {
|
||||||
|
const url = generateUrl(protocol, '/test/uselocal');
|
||||||
|
proxyGet(url, {})
|
||||||
|
.then(res => {
|
||||||
|
expect(res.body).toEqual(expectedLocalBody);
|
||||||
|
expect(res.headers['via-proxy-local']).toEqual('true');
|
||||||
|
done();
|
||||||
|
}, error => {
|
||||||
|
console.log('error happened in proxy get for shouldUseLocal: ',error);
|
||||||
|
done.fail('error happened when test shouldUseLocal rule');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
274
test/server/server.js
Normal file
274
test/server/server.js
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
const KoaRouter = require('koa-router');
|
||||||
|
const koaBody = require('koa-body');
|
||||||
|
const send = require('koa-send');
|
||||||
|
const path = require('path');
|
||||||
|
const https = require('https');
|
||||||
|
const certMgr = require("../../lib/certMgr");
|
||||||
|
const fs = require('fs');
|
||||||
|
const websocket = require('koa-websocket');
|
||||||
|
const wsRouter = require('koa-router')();
|
||||||
|
const color = require('colorful');
|
||||||
|
const WebSocketServer = require('ws').Server;
|
||||||
|
|
||||||
|
const DEFAULT_PORT = 3000;
|
||||||
|
const HTTPS_PORT = 3001;
|
||||||
|
const UPLOAD_DIR = './test/temp';
|
||||||
|
const PROXY_KEY_PREFIX = 'proxy-';
|
||||||
|
|
||||||
|
function KoaServer() {
|
||||||
|
this.httpServer = null;
|
||||||
|
this.httpsServer = null;
|
||||||
|
this.requestRecordMap = {}; // store all request data to the map
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* log the request info, write as
|
||||||
|
*/
|
||||||
|
this.logRequest = function* (next) {
|
||||||
|
const headers = this.request.headers;
|
||||||
|
let key = this.request.host + this.request.url;
|
||||||
|
|
||||||
|
// take proxy data with 'proxy-' + url
|
||||||
|
if (headers['via-proxy'] === 'true') {
|
||||||
|
key = PROXY_KEY_PREFIX + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = this.request.body;
|
||||||
|
body = typeof body === 'object' ? JSON.stringify(body) : body;
|
||||||
|
self.requestRecordMap[key] = {
|
||||||
|
headers: headers,
|
||||||
|
body: body
|
||||||
|
};
|
||||||
|
yield next;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
KoaServer.prototype.constructRouter = function() {
|
||||||
|
const router = KoaRouter();
|
||||||
|
router.post('/test/getuser', this.logRequest, koaBody(), function*(next) {
|
||||||
|
printLog('requesting post /test/getuser');
|
||||||
|
this.response.set('reqbody', JSON.stringify(this.request.body));
|
||||||
|
this.response.body = 'something in post';
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test', this.logRequest, function*(next) {
|
||||||
|
printLog('request in get: ' + JSON.stringify(this.request));
|
||||||
|
this.response.body = 'something';
|
||||||
|
this.response.__req = this.request;
|
||||||
|
printLog('response in get:' + JSON.stringify(this.response));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test/uselocal', this.logRequest, function*(next) {
|
||||||
|
printLog('request in get local:' + JSON.stringify(this.request));
|
||||||
|
this.response.body = 'something should be in local';
|
||||||
|
this.response.__req = this.request;
|
||||||
|
printLog('response in get:' + JSON.stringify(this.response));
|
||||||
|
});
|
||||||
|
|
||||||
|
['png', 'webp', 'json', 'js', 'css', 'ttf', 'eot', 'svg', 'woff', 'woff2'].forEach(item => {
|
||||||
|
router.get(`/test/download/${item}`, this.logRequest, function* (next) {
|
||||||
|
printLog(`now downloading the ${item}`);
|
||||||
|
yield send(this, `test/data/test.${item}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test/response/303', function*(next) {
|
||||||
|
printLog('now to redirect 303');
|
||||||
|
this.redirect('/test');
|
||||||
|
this.status = 303;
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test/response/302', function*(next) {
|
||||||
|
printLog('now to redirect 302');
|
||||||
|
this.redirect('/test');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/test/response/301', function*(next) {
|
||||||
|
printLog('now to redirect permanently');
|
||||||
|
this.redirect('/test');
|
||||||
|
this.status = 301;
|
||||||
|
});
|
||||||
|
|
||||||
|
const onFileBegin = function(name, file) {
|
||||||
|
if (!fs.existsSync('./test/temp')) {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync('./test/temp', '0777');
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file.name = 'test_upload_' + Date.now() + '.png';
|
||||||
|
var folder = path.dirname(file.path);
|
||||||
|
file.path = path.join(folder, file.name);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
router.post('/test/upload/png',
|
||||||
|
this.logRequest,
|
||||||
|
koaBody({
|
||||||
|
multipart: true,
|
||||||
|
formidable: {
|
||||||
|
uploadDir: UPLOAD_DIR,
|
||||||
|
onFileBegin: onFileBegin
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
function*(next) {
|
||||||
|
const file = this.request.body.files.file;
|
||||||
|
this.response.set('reqbody', JSON.stringify(this.request.body.fields));
|
||||||
|
this.response.body = file.path;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put('/test/upload/putpng',
|
||||||
|
this.logRequest,
|
||||||
|
koaBody({
|
||||||
|
multipart: true,
|
||||||
|
formidable: {
|
||||||
|
uploadDir: UPLOAD_DIR,
|
||||||
|
onFileBegin: onFileBegin
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
function*(next) {
|
||||||
|
const file = this.request.body.files.file;
|
||||||
|
this.response.body = file.path;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.put('/test/put', koaBody(), this.logRequest, function*(next) {
|
||||||
|
printLog('requesting put /test/put' + JSON.stringify(this.request));
|
||||||
|
this.response.body = 'something in put';
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/test/delete/:id', this.logRequest, function*(next) {
|
||||||
|
printLog('requesting delete /test/delete/:id'+ JSON.stringify(this.params));
|
||||||
|
this.response.body = 'something in delete';
|
||||||
|
});
|
||||||
|
|
||||||
|
router.head('/test/head', this.logRequest, function*(next) {
|
||||||
|
printLog('requesting head /test/head');
|
||||||
|
this.response.body = ''; // the body will not be passed to response, in HEAD request
|
||||||
|
this.response.set('reqBody', 'head_request_contains_no_resbody');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.options('/test/options', this.logRequest, function*(next) {
|
||||||
|
printLog('requesting options /test/options');
|
||||||
|
this.response.body = 'could_be_empty';
|
||||||
|
this.response.set('Allow', 'GET, HEAD, POST, OPTIONS');
|
||||||
|
});
|
||||||
|
|
||||||
|
// router.connect('/test/connect', function *(next) {
|
||||||
|
// printLog('requesting connect /test/connect');
|
||||||
|
// this.response.body = 'connect_established_body';
|
||||||
|
// });
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.constructWsRouter = function() {
|
||||||
|
const wsRouter = KoaRouter();
|
||||||
|
const self = this;
|
||||||
|
wsRouter.get('/test/socket', function*(next) {
|
||||||
|
const ws = this.websocket;
|
||||||
|
const messageObj = {
|
||||||
|
type: 'initial',
|
||||||
|
content: 'default message'
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.send(JSON.stringify(messageObj));
|
||||||
|
ws.on('message', message => {
|
||||||
|
printLog('message from request socket: ' + message);
|
||||||
|
self.handleRecievedMessage(ws, message);
|
||||||
|
});
|
||||||
|
yield next;
|
||||||
|
});
|
||||||
|
|
||||||
|
return wsRouter;
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.getRequestRecord = function (key) {
|
||||||
|
return this.requestRecordMap[key] || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.getProxyRequestRecord = function (key) {
|
||||||
|
key = PROXY_KEY_PREFIX + key;
|
||||||
|
return this.requestRecordMap[key] || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.handleRecievedMessage = function(ws, message) {
|
||||||
|
const newMessage = {
|
||||||
|
type: 'onMessage',
|
||||||
|
content: message
|
||||||
|
};
|
||||||
|
ws.send(JSON.stringify(newMessage));
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.start = function() {
|
||||||
|
printLog('Starting the server...');
|
||||||
|
const router = this.constructRouter();
|
||||||
|
const wsRouter = this.constructWsRouter();
|
||||||
|
const self = this;
|
||||||
|
const app = Koa();
|
||||||
|
websocket(app);
|
||||||
|
|
||||||
|
app.use(router.routes());
|
||||||
|
app.ws.use(wsRouter.routes());
|
||||||
|
this.httpServer = app.listen(DEFAULT_PORT);
|
||||||
|
|
||||||
|
printLog('HTTP is now listening on port :' + DEFAULT_PORT);
|
||||||
|
|
||||||
|
certMgr.getCertificate('localhost', function(error, keyContent, crtContent) {
|
||||||
|
if (error) {
|
||||||
|
console.error('failed to create https server:', error);
|
||||||
|
} else {
|
||||||
|
self.httpsServer = https.createServer({
|
||||||
|
key: keyContent,
|
||||||
|
cert: crtContent
|
||||||
|
}, app.callback());
|
||||||
|
|
||||||
|
// create wss server
|
||||||
|
const wss = new WebSocketServer({
|
||||||
|
server: self.httpsServer
|
||||||
|
});
|
||||||
|
|
||||||
|
wss.on('connection', function connection(ws) {
|
||||||
|
ws.on('message', function incoming(message) {
|
||||||
|
printLog('received in wss: ' + message);
|
||||||
|
self.handleRecievedMessage(ws, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
wss.on('error', error => {
|
||||||
|
console.error('erro happened in wss:%s', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.httpsServer.listen(HTTPS_PORT);
|
||||||
|
|
||||||
|
printLog('HTTPS is now listening on port :' + HTTPS_PORT);
|
||||||
|
|
||||||
|
printLog('Server started successfully');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
KoaServer.prototype.close = function() {
|
||||||
|
printLog('Closing server now...');
|
||||||
|
this.httpServer && this.httpServer.close();
|
||||||
|
this.httpsServer && this.httpsServer.close();
|
||||||
|
this.requestRecordMap = {};
|
||||||
|
printLog('Server closed successfully');
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function printLog(content) {
|
||||||
|
console.log(color.cyan('===SERVER LOG===' + content));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = KoaServer;
|
3
test/server/startServer.js
Normal file
3
test/server/startServer.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const Server = require('./server.js');
|
||||||
|
|
||||||
|
new Server();
|
@ -1,5 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
echo "nodeunit is required to run these test cases"
|
echo "Begin to run the test suites, JASMINE is required.\n"
|
||||||
|
echo "Removing test temp directory before running"
|
||||||
|
rm -rf ./test/temp/*
|
||||||
|
echo "Removing done, test cases now running"
|
||||||
node -v
|
node -v
|
||||||
nodeunit test.js
|
jasmine JASMINE_CONFIG_PATH=./jasmine.json
|
||||||
|
22
test/test_rules/shouldUseLocalResponseRule.js
Normal file
22
test/test_rules/shouldUseLocalResponseRule.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Rule defination for shouldUseLocalResponse
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
const dealLocalBody = 'handled_in_local_response';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
shouldUseLocalResponse: function (req, reqBody) {
|
||||||
|
return req.url.indexOf('uselocal') > -1;
|
||||||
|
},
|
||||||
|
shouldInterceptHttpsReq: function () {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
dealLocalResponse: function (req, reqBody, callback) {
|
||||||
|
const header = {
|
||||||
|
'Via-Proxy-Local': 'true'
|
||||||
|
};
|
||||||
|
|
||||||
|
callback(200, header, dealLocalBody);
|
||||||
|
}
|
||||||
|
};
|
153
test/util/CommonUtil.js
Normal file
153
test/util/CommonUtil.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* The utility class for test
|
||||||
|
*/
|
||||||
|
const zlib = require('zlib');
|
||||||
|
const color = require('colorful');
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare whether tow object are equal
|
||||||
|
*/
|
||||||
|
function isObjectEqual (source = {} , target = {}, url = '') {
|
||||||
|
source = Object.assign({}, source);
|
||||||
|
target = Object.assign({}, target);
|
||||||
|
let isEqual = true;
|
||||||
|
|
||||||
|
for(const key in source) {
|
||||||
|
isEqual = isEqual && source[key] === target[key];
|
||||||
|
|
||||||
|
if (!isEqual) {
|
||||||
|
console.info('source object :', source);
|
||||||
|
console.info('target object :', target);
|
||||||
|
printError(`different key in isObjectEqual is: "${key}", source is "${source[key]}",
|
||||||
|
target is "${target[key]}" the url is ${url}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete source[key];
|
||||||
|
delete target[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const key in target) {
|
||||||
|
isEqual = isEqual && source[key] === target[key];
|
||||||
|
|
||||||
|
if (!isEqual) {
|
||||||
|
console.info('source object :', source);
|
||||||
|
console.info('target object :', target);
|
||||||
|
printError(`different key in isObjectEqual is: "${key}", source is "${source[key]}",
|
||||||
|
target is "${target[key]}" the url is ${url}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete source[key];
|
||||||
|
delete target[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare the header between direct with proxy
|
||||||
|
* Will exclude the header(s) which modified by proxy
|
||||||
|
*/
|
||||||
|
function isCommonResHeaderEqual (directHeaders, proxyHeaders, requestUrl) {
|
||||||
|
directHeaders = Object.assign({}, directHeaders);
|
||||||
|
proxyHeaders = Object.assign({}, proxyHeaders);
|
||||||
|
let isEqual = true;
|
||||||
|
const mustEqualFileds = []; // the fileds that have to be equal, or the assert will be failed
|
||||||
|
|
||||||
|
if (!/gzip/i.test(directHeaders['content-encoding'])) {
|
||||||
|
// if the content is gzipped, proxy will unzip and remove the header
|
||||||
|
mustEqualFileds.push('content-encoding');
|
||||||
|
mustEqualFileds.push('content-length');
|
||||||
|
}
|
||||||
|
mustEqualFileds.push('content-type');
|
||||||
|
mustEqualFileds.push('cache-control');
|
||||||
|
mustEqualFileds.push('allow');
|
||||||
|
|
||||||
|
// ensure the required fileds are same
|
||||||
|
mustEqualFileds.forEach(filedName => {
|
||||||
|
isEqual = directHeaders[filedName] === proxyHeaders[filedName];
|
||||||
|
delete directHeaders[filedName];
|
||||||
|
delete proxyHeaders[filedName];
|
||||||
|
});
|
||||||
|
|
||||||
|
// remained filed are good to be same, but are allowed to be different
|
||||||
|
// will warn out those different fileds
|
||||||
|
for (const key in directHeaders) {
|
||||||
|
if (directHeaders[key] !== proxyHeaders[key]) {
|
||||||
|
printWarn(`key "${key}" of two response headers are different in request "${requestUrl}" :
|
||||||
|
direct is: "${directHeaders[key]}", proxy is: "${proxyHeaders[key]}"`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compare the request between direct with proxy
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function isCommonReqEqual(url, serverInstance) {
|
||||||
|
try{
|
||||||
|
url = url.replace('https://', '').replace('http://', ''); // only the remained path is required
|
||||||
|
let isEqual = true;
|
||||||
|
|
||||||
|
const directReqObj = serverInstance.getRequestRecord(url);
|
||||||
|
const proxyReqObj = serverInstance.getProxyRequestRecord(url);
|
||||||
|
|
||||||
|
// ensure the proxy header is correct
|
||||||
|
isEqual = isEqual && proxyReqObj.headers['via-proxy'] === 'true';
|
||||||
|
delete proxyReqObj.headers['via-proxy'];
|
||||||
|
|
||||||
|
// exclued accept-encoding from comparing, since the proxy will remove it before sending it out
|
||||||
|
delete directReqObj.headers['accept-encoding'];
|
||||||
|
|
||||||
|
// per undefined header, proxy will set it with 0, and an empty request body
|
||||||
|
if (typeof directReqObj.headers['content-length'] === 'undefined') {
|
||||||
|
|
||||||
|
directReqObj.headers['content-length'] = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
directReqObj.headers['content-type'] = trimFormContentType(directReqObj.headers['content-type']);
|
||||||
|
proxyReqObj.headers['content-type'] = trimFormContentType(proxyReqObj.headers['content-type']);
|
||||||
|
|
||||||
|
isEqual = isEqual && directReqObj.url === proxyReqObj.url;
|
||||||
|
isEqual = isEqual && isObjectEqual(directReqObj.headers, proxyReqObj.headers, url);
|
||||||
|
isEqual = isEqual && directReqObj.body === proxyReqObj.body;
|
||||||
|
return isEqual;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* for multipart-form, the boundary will be different with each update, we trim it here
|
||||||
|
*/
|
||||||
|
function trimFormContentType (contentType = '') {
|
||||||
|
return contentType.replace(/boundary.*/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function printLog (content) {
|
||||||
|
console.log(color.blue('==LOG==: ' + content));
|
||||||
|
}
|
||||||
|
|
||||||
|
function printWarn(content) {
|
||||||
|
console.log(color.magenta('==WARN==: ' + content));
|
||||||
|
}
|
||||||
|
|
||||||
|
function printError(content) {
|
||||||
|
console.log(color.red('==ERROR==: ' + content));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
isObjectEqual,
|
||||||
|
isCommonResHeaderEqual,
|
||||||
|
printLog,
|
||||||
|
printWarn,
|
||||||
|
printError,
|
||||||
|
isCommonReqEqual
|
||||||
|
};
|
262
test/util/HttpUtil.js
Normal file
262
test/util/HttpUtil.js
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
/**
|
||||||
|
* An util to make the request out
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const querystring = require('querystring');
|
||||||
|
const http = require('http');
|
||||||
|
const zlib = require('zlib');
|
||||||
|
const Buffer = require('buffer').Buffer;
|
||||||
|
const request = require('request');
|
||||||
|
const fs = require('fs');
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
const HttpsProxyAgent = require('https-proxy-agent');
|
||||||
|
|
||||||
|
const DEFAULT_HOST = 'localhost';
|
||||||
|
const PROXY_HOST = 'http://localhost:8001';
|
||||||
|
const SOCKET_PROXY_HOST = 'http://localhost:8001';
|
||||||
|
|
||||||
|
|
||||||
|
const HTTP_SERVER_BASE = 'http://localhost:3000';
|
||||||
|
const HTTPS_SERVER_BASE = 'https://localhost:3001';
|
||||||
|
const WS_SERVER_BASE = 'ws://localhost:3000';
|
||||||
|
const WSS_SERVER_BASE = 'wss://localhost:3001';
|
||||||
|
|
||||||
|
const DEFAULT_PROXY_OPTIONS = {
|
||||||
|
port: 8001, // proxy的端口
|
||||||
|
method: 'GET',
|
||||||
|
host: 'localhost'
|
||||||
|
};
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function getHostFromUrl (url = '') {
|
||||||
|
const hostReg = /^(https{0,1}:\/\/)(\w+)/;
|
||||||
|
const match = url.match(hostReg);
|
||||||
|
|
||||||
|
return match && match[2] ? match[2] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPortFromUrl (url = '') {
|
||||||
|
const portReg = /^https{0,1}:\/\/\w+(:(\d+)){0,1}/;
|
||||||
|
const match = url.match(portReg);
|
||||||
|
let port = match && match[2] ? match[2] : '';
|
||||||
|
|
||||||
|
if (!port) {
|
||||||
|
port = url.indexOf('https://') === 0 ? 443 : 80;
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取url中的path
|
||||||
|
*/
|
||||||
|
function getPathFromUrl (url = '') {
|
||||||
|
const pathReg = /^https{0,1}:\/\/\w+(:\d+){0,1}(.+)/;
|
||||||
|
const match = url.match(pathReg);
|
||||||
|
const path = match && match[3] ? match[2] : url;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyRequest (method = 'GET', url, params, headers = {}) {
|
||||||
|
return doRequest(method, url, params, headers, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 直接请求到真实服务器,不经过代理服务器
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function directRequest (method = 'GET', url, params, headers = {}) {
|
||||||
|
return doRequest(method, url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directUpload (url, filepath, formParams = {}, headers = {}) {
|
||||||
|
return doUpload(url, 'POST', filepath, formParams, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyUpload (url, filepath, formParams = {}, headers = {}) {
|
||||||
|
return doUpload(url, 'POST', filepath, formParams, headers, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directPutUpload (url, filepath, formParams = {}, headers = {}) {
|
||||||
|
return doUpload(url, 'PUT', filepath, formParams, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyPutUpload (url, filepath, headers = {}) {
|
||||||
|
return doUpload(url, 'PUT', filepath, headers, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doRequest (method = 'GET', url, params, headers = {}, isProxy) {
|
||||||
|
headers = Object.assign({}, headers);
|
||||||
|
const requestData = {
|
||||||
|
method: method,
|
||||||
|
form: params,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isProxy) {
|
||||||
|
requestData.proxy = PROXY_HOST;
|
||||||
|
requestData.headers['via-proxy'] = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestTask = new Promise((resolve, reject) => {
|
||||||
|
request(
|
||||||
|
requestData,
|
||||||
|
function (error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doUpload (url, method, filepath, formParams, headers = {}, isProxy) {
|
||||||
|
let formData = {
|
||||||
|
file: fs.createReadStream(filepath)
|
||||||
|
};
|
||||||
|
|
||||||
|
formData = Object.assign({}, formData, formParams);
|
||||||
|
headers = Object.assign({}, headers);
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
formData: formData,
|
||||||
|
url: url,
|
||||||
|
method: method,
|
||||||
|
headers: headers,
|
||||||
|
json: true,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isProxy) {
|
||||||
|
requestData.proxy = PROXY_HOST;
|
||||||
|
requestData.headers['via-proxy'] = 'true';
|
||||||
|
}
|
||||||
|
const requestTask = new Promise((resolve, reject) => {
|
||||||
|
request(
|
||||||
|
requestData,
|
||||||
|
function (error, response, body) {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(response);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return requestTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doWebSocket(url, isProxy) {
|
||||||
|
let ws;
|
||||||
|
if (isProxy) {
|
||||||
|
const agent = new HttpsProxyAgent(SOCKET_PROXY_HOST);
|
||||||
|
ws = new WebSocket(url, {
|
||||||
|
agent: agent,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ws = new WebSocket(url, {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ws;
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyGet (url, params, headers = {}) {
|
||||||
|
return proxyRequest('GET', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyPost (url, params, headers = {}) {
|
||||||
|
return proxyRequest('POST', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyPut (url, params, headers = {}) {
|
||||||
|
return proxyRequest('PUT', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyDelete (url, params, headers = {}) {
|
||||||
|
return proxyRequest('DELETE', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyHead(url, headers = {}) {
|
||||||
|
return proxyRequest('HEAD', url, {}, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyOptions(url, headers = {}) {
|
||||||
|
return proxyRequest('OPTIONS', url, {}, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directGet (url, params, headers = {}) {
|
||||||
|
return directRequest('GET', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directPost (url, params, headers = {}) {
|
||||||
|
return directRequest('POST', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directPut (url, params, headers = {}) {
|
||||||
|
return directRequest('PUT', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directDelete (url, params, headers = {}) {
|
||||||
|
return directRequest('DELETE', url, params, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directHead (url, headers = {}) {
|
||||||
|
return directRequest('HEAD', url, {} , headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directOptions (url, headers ={}) {
|
||||||
|
return directRequest('OPTIONS', url, {}, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyWs (url) {
|
||||||
|
return doWebSocket(url, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directWs (url) {
|
||||||
|
return doWebSocket(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* generate the final url based on protocol and path
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function generateUrl (protocol, urlPath) {
|
||||||
|
return protocol === 'http' ? HTTP_SERVER_BASE + urlPath : HTTPS_SERVER_BASE + urlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateWsUrl (protocol, urlPath) {
|
||||||
|
return protocol === 'wss' ? WSS_SERVER_BASE + urlPath : WS_SERVER_BASE + urlPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
proxyGet,
|
||||||
|
proxyPost,
|
||||||
|
directGet,
|
||||||
|
directPost,
|
||||||
|
directUpload,
|
||||||
|
proxyUpload,
|
||||||
|
generateUrl,
|
||||||
|
proxyWs,
|
||||||
|
directWs,
|
||||||
|
generateWsUrl,
|
||||||
|
directPut,
|
||||||
|
proxyPut,
|
||||||
|
directDelete,
|
||||||
|
proxyDelete,
|
||||||
|
directHead,
|
||||||
|
proxyHead,
|
||||||
|
directOptions,
|
||||||
|
proxyOptions,
|
||||||
|
directPutUpload,
|
||||||
|
proxyPutUpload
|
||||||
|
};
|
65
test/util/ProxyServerUtil.js
Normal file
65
test/util/ProxyServerUtil.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Utility class for creating proxy server, used to create specfied proxy server
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
let proxy = require('../../proxy.js');
|
||||||
|
const util = require('../../lib/util.js');
|
||||||
|
|
||||||
|
const DEFAULT_OPTIONS = {
|
||||||
|
type: "http",
|
||||||
|
port: 8001,
|
||||||
|
hostname: "localhost",
|
||||||
|
dbFile: null, // optional, save request data to a specified file, will use in-memory db if not specified
|
||||||
|
webPort: 8002, // optional, port for web interface
|
||||||
|
socketPort: 8003, // optional, internal port for web socket, replace this when it is conflict with your own service
|
||||||
|
throttle: 10000, // optional, speed limit in kb/s
|
||||||
|
disableWebInterface: false, //optional, set it when you don't want to use the web interface
|
||||||
|
setAsGlobalProxy: false, //set anyproxy as your system proxy
|
||||||
|
interceptHttps: true, // intercept https as well
|
||||||
|
silent: false //optional, do not print anything into terminal. do not set it when you are still debugging.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return An instance of proxy, could be closed by calling `instance.close()`
|
||||||
|
*/
|
||||||
|
function defaultProxyServer () {
|
||||||
|
proxy = util.freshRequire('../proxy.js');
|
||||||
|
|
||||||
|
const options = util.merge({}, DEFAULT_OPTIONS);
|
||||||
|
options.rule = util.freshRequire('./rule_default.js');
|
||||||
|
return new proxy.proxyServer(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create proxy server with rule
|
||||||
|
* @param rules
|
||||||
|
Object, the rule object which contains required intercept method
|
||||||
|
@return An instance of proxy, could be closed by calling `instance.close()`
|
||||||
|
*/
|
||||||
|
function proxyServerWithRule (rule) {
|
||||||
|
proxy = util.freshRequire('../proxy.js');
|
||||||
|
|
||||||
|
const options = util.merge({}, DEFAULT_OPTIONS);
|
||||||
|
options.rule = rule;
|
||||||
|
|
||||||
|
return new proxy.proxyServer(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function proxyServerWithoutHttpsIntercept (rule) {
|
||||||
|
proxy = util.freshRequire('../proxy.js');
|
||||||
|
|
||||||
|
const options = util.merge({}, DEFAULT_OPTIONS);
|
||||||
|
if (rule) {
|
||||||
|
options.rule = rule;
|
||||||
|
}
|
||||||
|
options.interceptHttps = false;
|
||||||
|
return new proxy.proxyServer(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
defaultProxyServer,
|
||||||
|
proxyServerWithoutHttpsIntercept,
|
||||||
|
proxyServerWithRule
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user