Package: x-man
XmanXmanis a HTTP proxy recording & replaying requests.It acts as an extensible "Man in the middle" server, which can:forward requests to other addressreturn cached results immediately without need to proxyingrecord incoming requests to a file, restore responses from theretransform requests & responses on the fly (eg. replace path with regex)throttle requests when clients are making them too frequentlyWithxmanyou can setup a mock server imitating a real server:Configure it to forward to a real server. Enable recording requests and replaying responses.Make some typical requests. Request-response entries will be recorded to a file.You can turn off a real server now. Responses are returned from cache.Usexmanwith recorded data to setup lighweight HTTP service mocks anywhere.Installationpip3installx-manPython 3.6 (or newer) is required.QuickstartConfigure listening on SSL port 8443, forwarding requests tohttps://127.0.0.1:8000with caching.
When the same request comes, cached response will be returned.$xmanhttps://127.0.0.1:8000--listen-port8443--listen-ssl=true--replay=true[2020-09-05 19:39:55] [INFO ] CACHE: loaded request-response pairs record_file=tape.json loaded=17 conflicts=0[2020-09-05 19:39:55] [INFO ] Listening on HTTPS port 8443... ssl=True addr= port=8443 destination=https://127.0.0.1:8000Run in dockerYou can runxmanin docker and pass your custom arguments at the end.That command just prints out the help:dockerrun--rm-it--network=hostigrek5151/xman:latestBasic forwarding all requests with rudimentary caching:dockerrun--rm-it--network=hostigrek5151/xman:latest\http://127.0.0.1:8000--listen-port8443--listen-ssl=true--replay=trueFor more customization create your ownext.pyextension file (example in section below) and run:dockerrun--rm-it--network=host-v`pwd`/ext.py:/ext.pyigrek5151/xman:latest\--config=/ext.pyIf you want to keep recorded requests & responses outside container, mounttape.jsonas well:touchtape.json
dockerrun--rm-it--network=host-v`pwd`/ext.py:/ext.py-v`pwd`/tape.json:/src/tape.jsonigrek5151/xman:latest\--config=/ext.py--record=true--replay=trueExtensionsIf you need more customization, you can specify extension file, where you can implement your custom behaviour or even processing logic.
In order to do that you must create Python script and pass its filename by parameter:xman --config ext.py.In extension file you can specify request / response mappers or custom comparator deciding which requests should be treated as the same. Using that you can achieve custom behaviour for some particular type of requests.Implement your function in place of one of the following functions:transform_request(request: HttpRequest) -> HttpRequest- Transforms each incoming Request before further processing (caching, forwarding).transform_response(request: HttpRequest, response: HttpResponse) -> HttpResponse- Transforms each Response before sending it.immediate_responder(request: HttpRequest) -> Optional[HttpResponse]- Returns immediate response for matched request instead of proxying it further or searching in cachecan_be_cached(request: HttpRequest, response: HttpResponse) -> bool- Indicates whether particular request with response could be saved in cache.cache_request_traits(request: HttpRequest) -> Tuple- Gets tuple denoting request uniqueness. Requests with same results are treated as the same when caching.override_config(config: Config)- Overrides default parameters in config.Extensions exampleext.pyfromtypingimportTuple,Optionalfromnuclear.sublogimportlogfromxman.cacheimportsorted_dict_traitfromxman.configimportConfigfromxman.requestimportHttpRequestfromxman.responseimportHttpResponsefromxman.transformimportreplace_request_pathdeftransform_request(request:HttpRequest)->HttpRequest:"""Transforms each incoming Request before further processing (caching, forwarding)."""returnreplace_request_path(request,r'^/some/path/(.+?)(/[a-z]+)(/.*)',r'\3')defimmediate_responder(request:HttpRequest)->Optional[HttpResponse]:"""Returns immediate response for matched request instead of proxying it further or searching in cache"""ifrequest.path.startswith('/some/api'):returnHttpResponse(status_code=200,headers={'Content-Type':'application/json'},content=''.encode())returnNonedeftransform_response(request:HttpRequest,response:HttpResponse)->HttpResponse:"""Transforms each Response before sending it."""ifrequest.path.startswith('/some/api'):log.debug('Found Ya',path=request.path)response=response.set_content('{"payload": "anythingyouwish"}"')returnresponsedefcan_be_cached(request:HttpRequest,response:HttpResponse)->bool:"""Indicates whether particular request with response could be saved in cache."""returnresponse.status_code==200defcache_request_traits(request:HttpRequest)->Tuple:"""Gets tuple denoting request uniqueness. Requests with same results are treated as the same when caching."""ifrequest.path.endswith('/some/path'):returnrequest.method,request.path,sorted_dict_trait(request.headers)returnrequest.method,request.path,request.contentdefoverride_config(config:Config):"""Overrides default parameters in config."""# config.listen_port = 8080# config.listen_ssl = True# config.dst_url = 'http://127.0.0.1:8000'# config.record = False# config.record_file = 'tape.json'# config.replay = False# config.replay_throttle = False# config.replay_clear_cache = False# config.replay_clear_cache_seconds = 60# config.allow_chunking = True# config.proxy_timeout = 10config.verbose=0UsageSee help by typingxman:xman v0.1.2 (nuclear v1.1.9) - HTTP proxy recording & replaying requestsUsage:xman [OPTIONS] [DST_URL]Arguments:[DST_URL] - destination base urlDefault: http://127.0.0.1:8000Options:--version                                               - Print version information and exit-h, --help [SUBCOMMANDS...]                             - Display this help and exit--listen-port LISTEN_PORT                               - listen port for incoming requestsDefault: 8080--listen-ssl LISTEN_SSL                                 - enable https on listening sideDefault: True--record RECORD                                         - enable recording requests & responsesDefault: False--record-file RECORD_FILE                               - filename with recorded requestsDefault: tape.json--replay REPLAY                                         - return cached results if foundDefault: False--replay-throttle REPLAY_THROTTLE                       - throttle response if too many requests are madeDefault: False--replay-clear-cache REPLAY_CLEAR_CACHE                 - enable clearing cache periodicallyDefault: False--replay-clear-cache-seconds REPLAY_CLEAR_CACHE_SECONDS - clearing cache interval in secondsDefault: 60--allow-chunking ALLOW_CHUNKING                         - enable sending response in chunksDefault: True--config CONFIG                                         - load extensions from Python file-v, --verbose                                           - show more details in output