UNPKG

sintexplicabo

Version:
520 lines (480 loc) 16.8 kB
/*** * RestHttp(s) v1.0.0 * * @write : Maroder * @date : 2020-10-09 * * Not3 : * Basic send http(s) query, these object that allow to mount a http query doesn't check integrity of data sending, * its just a wrap around the different options supported by node http(s). for the version 1.0.x only most popular * options are supported for wrapping data, SSL/TLS options are supported. Some check are making when you have ending * to informing the different options of the query with the `build` and `post` methods will make a minimal check of : * + Made a concat with hostname + query_params variable * + Will set "multipart/form-data" at Content-Type header property if this one is not defined. * + Proxy replace path and hostname * * much things staying to do ... but I make sure that a basic request will be made, * I tried to respect the typescript and object dev rules as much as possible, like Java IORestAssured * * exportable : * - HttpOptions<T> : T is ResHttp or ResHttps object * - RestHttp(s) : builder a new object with static constructor handler `options` * - Response : Getter class * * All errors message are redirecting to Response Object. */ import * as http from "http"; import * as https from "https"; import {JSONException} from '../Exception'; import {List, loader, restHttp, streamLambdaTo, wrapHeader} from "../Interface"; import {ArrayList, HashMap} from "../List"; import {Proxy} from "./Proxy"; import {Cookie} from "./Cookie"; import "../globalUtils" import {Define} from "../Define"; import {Constructor} from "../Constructor"; /** * */ export type httpProtoType = "https"|"http" export type httpMethodType = "HEAD"|"GET"|"PUT"|"POST"|"DELETE"|"CONNECT"|"TRACE"|"PATCH"; export type httpMimeType = "text/plain"|"text/html"|"text/xml"|"text/csv"|"application/octet-stream"|"application/xml"| "application/json"|"application/javascript"|"multipart/form-data"|"application/x-www-form-urlencoded"| "application/xhtml+xml"|"application/ld+json" //... /*** * I keep this static class in private for this moment, * but... it is only used to send requests */ class httpEvent{ /**** * + May be make a try/catch wrap on 'handle' variable, in order * to return a RuntimeException when an error is throwing. * * @param rest */ public static async send( rest : AbstractRestHttp ):Promise<Response>{ let httpA : any = (rest.getProto().equals("http")?http:https), handle; return new Promise<Response>((resolve,reject)=>{ handle = httpA.request( rest.getHeaderAsObject().toJson(), response=>{ let chunk = []; if(rest.getLoader()&&response.statusCode!==200)rest.getLoader().error(`Failed to download resources, status code : ${response.statusCode}`); else if(rest.getLoader()&&response.statusCode===200) { let s:number = parseInt(new Response({headers: response.headers}).getHeader("content-length"))||0; rest.getLoader().setSizeOf(s); } response.on("data",data=>{ chunk.push(data); if(rest.getLoader()&&response.statusCode===200)rest.getLoader().add(data.length); }); response.on("end",()=>{httpEvent.pack(resolve,response,Buffer.concat(chunk).toString(rest.getEncoding()));}); }).on("error",error=>{ reject(new Response({ errorno:1,error:error })); }); if(rest.getData()){ try{ handle.write(rest.getData());}catch (e) { reject(new Response( { errorno:1,error:e })); } } handle.end(); }); } /*** * Repack response data * @param resolve * @param response * @param body */ private static pack( resolve : Function, response : any, body : string ): void{ resolve(new Response({ errorno:0,error:null,statusMessage: response.statusMessage, statusCode:response.statusCode, headers: response.headers, body: body })); } } /*** * RESPONSE CLASS * Wrap getter http response */ export class Response { /*** * */ private response : any = null; /*** * @param response */ constructor( response : any ) { this.response = response;} /*** * */ public getCookies() : ArrayList<Cookie> { let mapToCookie : streamLambdaTo<string,Cookie>=value=>Cookie.parse(value); return ArrayList.of<string>( this.getHeader("set-cookie") ) .stream() .mapTo(mapToCookie) .getList(); } /*** * */ public getCookie( name :string ) : Cookie { return this.getCookies() .stream() .filter(cookie=>cookie.getValue().equals(name)) .findFirst() .orElse(null); } /*** * */ public getHeaders() : HashMap<string,any>{ return HashMap.of<string,any>(this.response.headers); } /*** * */ public getHeader( name :string) : any{ try{return this.response.headers[name]}catch (e) { return null; } } /*** * */ public getBody():string{ return this.response.body;} /*** * throwable : RuntimeException * */ public getBodyAsObject():any{ try{return JSON.parse(this.response.body);}catch (e) { throw new JSONException(e); } } /*** * */ public getStatusCode( ): Number{ return Number(this.response.statusCode); } /*** * */ public getStatusMessage( ): string{ return this.response.statusMessage; } /*** * */ public contentType() : string{ return this.getHeader("Content-Type")||this.getHeader("content-type"); } /*** * */ public hasError( ): boolean{ return this.response.errorno>0; } /*** * */ public getLasError( ): string{ return this.response.error; } /*** * * @param code */ public success( code : number ): Define<Response>{ return new Define<Response>( this.getStatusCode().equals(code) ? this : null ); } } /*** * */ abstract class AbstractRestHttp implements restHttp{ /*** * */ protected encoding:BufferEncoding = "utf-8"; protected proto :httpProtoType = null; protected header :HashMap<string,any> = null; protected data :string = null; private loader:loader = null; /*** * */ public getProto( ): string{ return String(this.proto);} /*** * */ public getHeaderAsObject( ) : HashMap<string,any> {return this.header} /*** * */ public getDataAsObject( ) :any{ try{return JSON.parse(this.data);}catch (e) { return null; } } /*** * */ public getData( ): string { return this.data;} /*** * @async */ public async request( ) : Promise<Response>{ try{ if(this.loader) this.loader.start("Download was successful !"); return await httpEvent.send(this); } catch (Exception) { return Exception; } } public setData(data: string): void { this.data = data; } public setHeader(header: HashMap<string, any>): void { this.header = header} public setEncoding( encoding:BufferEncoding):restHttp{this.encoding = encoding; return this;} public getEncoding( ):BufferEncoding{ return this.encoding;} public setLoader( pipe:loader): restHttp { this.loader = pipe; return this; } public getLoader( ): loader{ return this.loader; } } export class RestHttp extends AbstractRestHttp{ /*** * */ protected proto : httpProtoType = "http"; /*** * @param header * @param data */ constructor( header : HashMap<string,any> = null, data : string = null) { super(); this.header = header; this.data = data; } /*** * Constructor */ public static options( ) : HttpOptions<RestHttp>{ return new HttpOptions<RestHttp>( RestHttp ); } } export class RestHttps extends RestHttp{ /*** * */ protected proto : httpProtoType = "https"; /*** * @param header * @param data */ constructor(header : HashMap<string,any>, data : string) {super(header,data); } /*** * Constructor */ public static options( ) : HttpOptions<RestHttps>{ return new HttpOptions<RestHttps>( RestHttps ); } } /*** * Only most popular options are implemented, * you can add new options yourself it not prohibited. */ export class HttpOptions<T extends restHttp> implements wrapHeader<T>{ private options : HashMap<string,any> = new HashMap<string,any>({}); private data : object|string = null; private params : string = ""; private Class : Constructor<T>; constructor( value : Function ){ this.Class = value.class(); this.options.put("headers",new HashMap<string,string>({})); this.withEndPoint("/"); this.withPort(this.Class.newInstance().getProto().equals("https") ? 443 : 80 ); } /*** * */ public withHostname( hostname : string ) : HttpOptions<T>{ this.options.put("hostname",hostname); return this; } public withPort( port : number): HttpOptions<T>{ this.options.put("port",port); return this; } public withEndPoint(endPoint : string): HttpOptions<T>{ this.options.put("path",endPoint); return this; } public withTimeout( timeout : number ): HttpOptions<T>{ this.options.put("timeout",timeout); return this; } /*** * Method Area */ public withMethod(method : httpMethodType ): HttpOptions<T>{ this.options.put("method",method); return this; } public get( ) : HttpOptions<T>{return this.withMethod("GET");} public put( ) : HttpOptions<T>{return this.withMethod("PUT");} public delete( ) : HttpOptions<T>{return this.withMethod("DELETE");} public post( ) : HttpOptions<T>{ if( !this.options.get("headers").get("Content-Type") )this.widthContentType("multipart/form-data"); return this.withMethod("POST"); } public withInsecureHTTPParser( state : boolean ) : HttpOptions<T>{ this.options.put("insecureHTTPParser",state); return this; } /*** * Headers Are */ public withHeader(type :string, value : any ): HttpOptions<T>{ this.options.get("headers").put( type, value); return this; } public widthContentType( mime : httpMimeType|string ): HttpOptions<T> { return this.withHeader("Content-Type",mime); } public widthAccept( mime : httpMimeType|string ): HttpOptions<T> { return this.withHeader("Accept", mime ); } public widthJson( mime : httpMimeType|string ): HttpOptions<T> { this.withHeader("Accept", "application/json" ); return this.widthContentType("application/json" ); } /*** * Auth Area */ public withAuth( auth : string ): HttpOptions<T>{ this.options.put("auth",auth); return this; } /*** * SSL/TLS options * */ public withRejectUnauthorized( state : boolean ): HttpOptions<T>{ this.options.put("rejectUnauthorized",state); return this; } public withRequestCert( state : boolean ): HttpOptions<T> { this.options.put("requestCert",state); return this; } public withPfx( pfx : Buffer|string ): HttpOptions<T>{ this.options.put("pfx",pfx); return this; } public withPassPhrase( passphrase : string ): HttpOptions<T>{ this.options.put("passphrase",passphrase); return this; } public withCACert( cacert : Buffer|string ): HttpOptions<T>{ this.options.put("cacert",cacert); return this; } public withCert( cert : Buffer|string ): HttpOptions<T>{ this.options.put("cert",cert); return this; } public withDhparam( dhparam : string ): HttpOptions<T>{ this.options.put("dhparam",dhparam); return this; } public withCrl( crl : Buffer|string ): HttpOptions<T>{ this.options.put("crl",crl); return this; } public withSSlKey( key : string ): HttpOptions<T>{ this.options.put("key",key); return this; } public withCiphers( ciphers : Array<String> ) : HttpOptions<T>{ this.options.put("ciphers",ciphers.join(":")); return this; } public withSessionTimeout( sessionTimeout : number ) : HttpOptions<T>{ this.options.put("sessionTimeout",sessionTimeout); return this; } public withSecureOptions( value : number ): HttpOptions<T>{ this.options.put("secureOptions",value); return this; } public withSigalgs( value : string ): HttpOptions<T>{ this.options.put("sigalgs",value); return this; } /*** * @withProxy : * httpProxy has [http_proxy_port] -> [http_proxy_port] who reflect => 80 * httpsProxy has [https_proxy_port] -> [http_proxy_port] who reflect => 443 * now query : * httpProxy -> with httpsQuery -> gotta reverse redirect protocol, otherwise follow same proto than proxy * port has defined in the redirect url : port !== ( [http_proxy_port] & 80 ) * * + can be an reverse redirect protocol or simply an another server with no https, * just a another service hosted under a other port than 80 or [http_proxy_port]. * * httpsProxy : same than httpProxy * @param proxy * @param noReverseProtocolFor */ public withProxy( proxy : Proxy, noReverseProtocolFor: List<number> = new ArrayList() ) : HttpOptions<T>{ let url:string = proxy.getHttpProxy(), host:string = this.options.get("hostname")||this.options.get("host"), proto:string = this.Class.newInstance().getProto(), originalPort:number = <number>proto.equals("http").state(80,443), port:number, tmp:List<string>; if(proto.equals("https")) url = proxy.getHttpsProxy(); // define proxy port => url:port if((tmp=url.explodeAsList(":")).size().equals(2)){ port = Number( tmp.get(1).orDefault(proto.equals("https").state("8443","8080")) ); url = tmp.get(0); }else // no port defined may be properties is defined withPort method // otherwise default port 8080 & 8443 port = Define.of<number>(this.options.get("port")) .orNull(proto.equals("http").state(8080,8443)) if((tmp=host.explodeAsList(/:(\d+)$/)).size().equals(3)){ if(!Number(tmp.get(1)).equals(originalPort)&&noReverseProtocolFor.indexOf(<Object>Number(tmp.get(1))).equals(-1)) proto = proto.equals("http")?"https":"http"; } return this.withEndPoint(proto+"://"+host+this.options.get("path")) .withPort( port ) .withHeader("Referer",this.options.get("path")) .withHeader("Host",host) .withHostname(url); } /*** * * @param params */ private packParams( params : object ): string{ let chunk=""; HashMap.of<string,any>(params).each((value,key)=>chunk += "%s=%s&".format(key,encodeURI(String(value)))); return chunk.replace(/\&$/,""); } public withPostData( data : object|string ): HttpOptions<T>{ return this.withData(data,true); } /*** * Method throwable : JSON.Parse * @param data * @param post */ public withData(data : object|string, post: boolean = false ): HttpOptions<T>{ let dat=""; switch (typeof data) { case "object": dat = post? this.packParams(data):JSON.stringify(data); break; case "string": dat = data; break; } this.data = data; return this.withHeader("Content-Length",dat.length); } /** * @param params */ public withParams( params : object ): HttpOptions<T>{ this.params = "%s%s".format("?",this.packParams(params)); return this; } /*** * @constructor */ public build() : T{ if(!this.params.isEmpty()) this.options.put("path", (this.options.get("path")||"/")+this.params ); return this.Class.newInstance(this.options,JSON.stringify(this.data)); } }