Class ScriptedProxy
- All Implemented Interfaces:
ChainedProxyManager,HttpFiltersSource,ProxyAuthenticator
ScriptedProxy script = ScriptedProxy.builder(Path.of("proxy.star")).build();
MicroProxy.bootstrap().withFiltersSource(script).withChainProxyManager(script).start();
Install it as the ProxyAuthenticator too when the script defines
authenticate (definesAuthenticate()).
The script may define any of these functions; each is optional:
on_request(req, ctx): inspect or change a request. Returnresponse(...)to answer it without contacting the server. Also called forCONNECT.on_response(req, res, ctx): inspect or change a response; may return a newresponse(...)to replace it.upstream(req, ctx): choose the route. ReturnNonefor the default,"DIRECT", a proxy URL such as"socks5://host:1080", or a list of these to try in order.allow_mitm(req, ctx): for aCONNECT, whether to intercept it (when a MITM manager is configured).on_client_hello(hello, ctx): once the client of aCONNECTthat may be intercepted (or of a transparent TLS connection, or a transparent QUIC flow, whose ClientHello the proxy reads from its Initial packets) has sent its TLS ClientHello, whether to intercept it:Falsetunnels (or relays) it untouched,TrueorNoneintercepts.hellohassni,alpn,versionsandcipher_suites; later hooks of the session see it asctx.client_hello.buffer_request(req, ctx): whether to buffer this request's body soon_requestcan readreq.body. Default: no.buffer_response(req, res, ctx): whether to buffer this response's body. Default: text bodies in a coding the proxy can decode, except event streams.on_websocket_frame(req, frame, ctx): for each frame of an upgraded WebSocket connection, in both directions. Assignframe.textorframe.payloadto change it; returnFalseto drop it, orNoneto forward it.on_websocket_message(msg, ctx): for each whole message (text or binary) of an upgraded WebSocket connection, joined from its frames and inflated, in both directions, afteron_websocket_framehas seen its frames. As withon_frame: changemsg.textormsg.payloadin place and returnNone(or the message) to send it,Falseto drop it, another message to replace it, or a list of messages (websocket_message(content, from_client=...)) to send instead, each in the direction itsfrom_clientnames (an empty list drops it).authenticate(req, ctx): decides whether a client may use the proxy, when this object is also installed as theProxyAuthenticator(withProxyAuthenticator(script)). Return the user name (a non-empty string) orTrueto accept,FalseorNoneto reject with the default407, orresponse(...)to reject with that answer. Anything else, a failure or a timeout rejects. A globalAUTHENTICATE_EVERY_REQUEST = Trueauthenticates every request instead of the first accepted one on each connection.on_failure(req, failure, ctx): when the proxy has to answer the request itself (the server's name did not resolve, the connection was refused, the server timed out, ...). Returnresponse(...)to answer, orNoneto leave it to theFailureResponderor the default answer.on_frame(frame, ctx): for each HTTP/2 and HTTP/3 frame, in each direction, when this object'sframeInterceptor()is installed (withFrameInterceptor): live where the proxy speaks the protocol, and for HTTP/3 frames run through anHttp3FramePipelinewith it. Change the frame in place and returnNone(or the frame) to pass it on,Falseto drop it, another frame to replace it, or a list of frames to replace it with (an empty list drops it). The rules ofFrameInterceptorapply; a result that breaks them is logged, and the frame passed on unchanged.on_tcp_start(ctx),on_tcp_message(msg, ctx)andon_tcp_end(ctx): for raw TCP tunnels (seeTcpInterceptor), when this object'stcpInterceptor()is installed (withTcpInterceptor).msgis aTcpMessage:data,text,from_clientandtimestamp. Change it in place and returnNone(orTrue, or the message) to pass it on,Falseto drop it, or bytes, a string or another message to replace it.ctxis aTcpContext: the target,tls,sni,alpn,varsfor the tunnel,errorinon_tcp_endafter a failure, andsend(from_client, data)to add bytes. A failingon_tcp_messagepasses the bytes on unchanged.on_udp_start(ctx),on_udp_message(msg, ctx)andon_udp_end(ctx): for the datagrams of CONNECT-UDP flows (seeUdpInterceptor), when this object'sudpInterceptor()is installed (withUdpInterceptor).msgis aUdpMessage, one datagram, with the fields and results of aTcpMessage;ctxis aUdpContext: the target, its resolvedaddress,varsfor the flow, andsend(from_client, data)to add a datagram.on_quic_start(ctx),on_quic_stream_data(msg, ctx),on_quic_datagram(msg, ctx)andon_quic_end(ctx): for QUIC intercepted stream by stream (seeQuicInterceptor), when this object'squicInterceptor()is installed (withQuicInterceptor).msgis aQuicMessage, with the fields and results of aTcpMessage: bytes of one read of a stream, or one datagram.ctxis aQuicContext: the server,sni,alpn,masque, the stream (stream_id,bidirectional,client_initiated;Noneoutsideon_quic_stream_data),varsfor the connection,errorinon_quic_endafter a failure,send(from_client, data)to add bytes to the stream andsend_datagram(from_client, data)to add a datagram.on_quic_message(msg, ctx): for each whole message of a stream that the proxy'sQuicFramingframes (DNS over QUIC by default), instead ofon_quic_stream_data:msg.datais the payload, without its length prefix or delimiter, andmsg.stream_idnames the stream. As withon_websocket_message: changemsgin place and returnNone(or the message) to send it,Falseto drop it, another message, bytes or a string to replace it, or a list of them to send instead, each message in the direction itsfrom_clientnames (an empty list drops it). Each is framed again, its length prefix computed afresh.quic_message_length(data): a framer of the script's own, which framing rules namescript(quicFramer()): given the bytes kept of a stream direction, the length of the message they start with (the message is given toon_quic_messagewhole), orNoneuntil the bytes say.
A failing hook is logged and answered with 500; a failing allow_mitm or
on_client_hello declines interception, a failing upstream rejects the request with 502, and a
failing on_failure leaves the answer to the responder or the default. When
the script is a file it is re-read when it changes (checked at most once a second); a version
that does not compile is logged and the previous one stays in use.
-
Nested Class Summary
Nested Classes -
Method Summary
Modifier and TypeMethodDescriptionbooleanauthenticate(String userName, String password) Not used:authenticate(HttpRequest, FlowContext)asks the script instead.authenticate(HttpRequest request, FlowContext flow) Calls the script'sauthenticate(req, ctx).booleanWhether the script setsAUTHENTICATE_EVERY_REQUESTto a true value.static ScriptedProxy.BuilderA proxy driven bysource, for embedding and tests.static ScriptedProxy.BuilderA proxy driven by the script infile.booleanWhether the script in use definesauthenticate, so this can be the authenticator.booleanWhether the script in use defineson_frame, soframeInterceptor()is worth installing.booleanWhether the script in use definesquic_message_length, a framer of its own forquicFramer().booleanWhether the script in use defineson_quic_start,on_quic_stream_data,on_quic_message,on_quic_datagramoron_quic_end, soquicInterceptor()is worth installing.booleanWhether the script in use defineson_tcp_start,on_tcp_messageoron_tcp_end, sotcpInterceptor()is worth installing.booleanWhether the script in use defineson_udp_start,on_udp_messageoron_udp_end, soudpInterceptor()is worth installing.filterRequest(HttpRequest originalRequest, FlowContext flowContext) Returns the filters for a request, ornullto proxy it unfiltered.An interceptor that calls the script'son_frame(frame, ctx), forHttpProxyServerBootstrap.withFrameInterceptor(org.microproxy.frames.FrameInterceptor)(live HTTP/2 and HTTP/3) orHttp3FramePipeline.builder(org.microproxy.frames.FrameInterceptor)(HTTP/3 streams relayed otherwise).voidlookupChainedProxies(HttpRequest request, Queue<ChainedProxy> chainedProxies, ClientDetails client) Adds the proxies to try, in order, tochainedProxies.AMessageFramerthat asks the script'squic_message_length(data)how long the message at the start of a stream direction's bytes is: an int (the whole message's length, which may be more than the bytes so far), orNoneuntil the bytes say.An interceptor that calls the script'son_quic_start(ctx),on_quic_stream_data(msg, ctx),on_quic_datagram(msg, ctx)andon_quic_end(ctx), forHttpProxyServerBootstrap.withQuicInterceptor(org.microproxy.quic.QuicInterceptor): QUIC intercepted stream by stream.script()The script in use, re-reading the file first if it changed.An interceptor that calls the script'son_tcp_start(ctx),on_tcp_message(msg, ctx)andon_tcp_end(ctx), forHttpProxyServerBootstrap.withTcpInterceptor(org.microproxy.tcp.TcpInterceptor).An interceptor that calls the script'son_udp_start(ctx),on_udp_message(msg, ctx)andon_udp_end(ctx), forHttpProxyServerBootstrap.withUdpInterceptor(org.microproxy.udp.UdpInterceptor): the datagrams of CONNECT-UDP flows.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface org.microproxy.HttpFiltersSource
getMaximumRequestBufferSizeInBytes, getMaximumResponseBufferSizeInBytesMethods inherited from interface org.microproxy.ProxyAuthenticator
getRealm
-
Method Details
-
builder
A proxy driven by the script infile.- Parameters:
file- the UTF-8 script file, reloaded on changes by default- Returns:
- a builder configured to load the file
-
builder
A proxy driven bysource, for embedding and tests.- Parameters:
source- the Starlark source to compilename- the script name used in diagnostics- Returns:
- a builder configured to compile the supplied source
-
script
The script in use, re-reading the file first if it changed.- Returns:
- the current successfully compiled script
-
filterRequest
Description copied from interface:HttpFiltersSourceReturns the filters for a request, ornullto proxy it unfiltered.- Specified by:
filterRequestin interfaceHttpFiltersSource- Parameters:
originalRequest- a copy of the request as received from the clientflowContext- the client connection the request arrived on- Returns:
- the per-request filters, or
nullto leave the request unfiltered
-
definesFrameHook
public boolean definesFrameHook()Whether the script in use defineson_frame, soframeInterceptor()is worth installing. A proxy without it pays nothing per frame.- Returns:
- whether the current script provides the frame hook
-
frameInterceptor
An interceptor that calls the script'son_frame(frame, ctx), forHttpProxyServerBootstrap.withFrameInterceptor(org.microproxy.frames.FrameInterceptor)(live HTTP/2 and HTTP/3) orHttp3FramePipeline.builder(org.microproxy.frames.FrameInterceptor)(HTTP/3 streams relayed otherwise). Frames pass on unchanged when the script in use (after a reload) has noon_frame, and when the call fails, which is logged.- Returns:
- the interceptor
-
definesUdpHooks
public boolean definesUdpHooks()Whether the script in use defineson_udp_start,on_udp_messageoron_udp_end, soudpInterceptor()is worth installing.- Returns:
- whether the current script provides a UDP hook
-
udpInterceptor
An interceptor that calls the script'son_udp_start(ctx),on_udp_message(msg, ctx)andon_udp_end(ctx), forHttpProxyServerBootstrap.withUdpInterceptor(org.microproxy.udp.UdpInterceptor): the datagrams of CONNECT-UDP flows. The calls of one flow, which come from a thread per direction, run one at a time, so they can sharectx.vars.on_udp_messagereturns what to send on:NoneorTruefor the message (as changed),Falseto drop it, or bytes, a string or a message to send instead. A datagram passes on unchanged when the script in use has noon_udp_message, and when the call fails, which is logged.- Returns:
- the interceptor
-
definesQuicHooks
public boolean definesQuicHooks()Whether the script in use defineson_quic_start,on_quic_stream_data,on_quic_message,on_quic_datagramoron_quic_end, soquicInterceptor()is worth installing. Installing it makes the proxy intercept QUIC that is not HTTP/3 (seeQuicInterceptor).- Returns:
- whether the current script provides a QUIC hook
-
quicInterceptor
An interceptor that calls the script'son_quic_start(ctx),on_quic_stream_data(msg, ctx),on_quic_datagram(msg, ctx)andon_quic_end(ctx), forHttpProxyServerBootstrap.withQuicInterceptor(org.microproxy.quic.QuicInterceptor): QUIC intercepted stream by stream. The calls of one connection, which come from a thread per direction of each stream and of datagrams, run one at a time, so they can sharectx.vars.on_quic_stream_dataandon_quic_datagramreturn what to send on:NoneorTruefor the message (as changed),Falseto drop it, or bytes, a string or a message to send instead. Bytes pass on unchanged when the script in use has no such hook, and when the call fails, which is logged.When the script defines
on_quic_messageas this is called, the interceptor also overridesQuicInterceptor.streamMessage(org.microproxy.quic.QuicMessage, org.microproxy.quic.QuicStreamContext), so the streams that the proxy'sQuicFramingframes go toon_quic_messageas whole messages instead of toon_quic_stream_data; a later reload that adds the hook takes effect after a restart, and one that removes it passes messages on unchanged.- Returns:
- the interceptor
-
definesQuicFramer
public boolean definesQuicFramer()Whether the script in use definesquic_message_length, a framer of its own forquicFramer().- Returns:
- whether the current script provides a framer
-
quicFramer
AMessageFramerthat asks the script'squic_message_length(data)how long the message at the start of a stream direction's bytes is: an int (the whole message's length, which may be more than the bytes so far), orNoneuntil the bytes say. The message goes toon_quic_messageas it is, and what that returns is sent as it is. A call that fails or returns something else makes the bytes malformed: the proxy resets the stream. Name it in the framing rules withQuicFraming.withFramer(java.lang.String, org.microproxy.quic.MessageFramer)(--scriptnames itscript:--quic-framing myproto=script).- Returns:
- the framer
-
definesTcpHooks
public boolean definesTcpHooks()Whether the script in use defineson_tcp_start,on_tcp_messageoron_tcp_end, sotcpInterceptor()is worth installing. A proxy without one copies tunnel bytes as before.- Returns:
- whether the current script provides a TCP hook
-
tcpInterceptor
An interceptor that calls the script'son_tcp_start(ctx),on_tcp_message(msg, ctx)andon_tcp_end(ctx), forHttpProxyServerBootstrap.withTcpInterceptor(org.microproxy.tcp.TcpInterceptor). The calls of one tunnel, which come from a thread per direction, run one at a time, so they can sharectx.vars. Bytes pass on unchanged when the script in use (after a reload) has noon_tcp_message, and when the call fails, which is logged.- Returns:
- the interceptor
-
definesAuthenticate
public boolean definesAuthenticate()Whether the script in use definesauthenticate, so this can be the authenticator.- Returns:
- whether the current script provides the authentication hook
-
authenticate
Not used:authenticate(HttpRequest, FlowContext)asks the script instead.- Specified by:
authenticatein interfaceProxyAuthenticator- Parameters:
userName- the authenticated user name, ornullif unknownpassword- the client password- Returns:
- whether the credentials are accepted
-
authenticate
Calls the script'sauthenticate(req, ctx). Fails closed: a script without the function (after a reload), a failing or timed-out call, or an unexpected return value rejects the request with the default challenge, and is logged.- Specified by:
authenticatein interfaceProxyAuthenticator- Parameters:
request- the request being handledflow- the client connection, with its address and TLS session- Returns:
AuthResult.accept(java.lang.String), orAuthResult.reject()with the answer to send
-
authenticateEveryRequest
public boolean authenticateEveryRequest()Whether the script setsAUTHENTICATE_EVERY_REQUESTto a true value.- Specified by:
authenticateEveryRequestin interfaceProxyAuthenticator- Returns:
- whether to authenticate every request rather than the first one accepted on each client connection, e.g. because tokens expire
-
lookupChainedProxies
public void lookupChainedProxies(HttpRequest request, Queue<ChainedProxy> chainedProxies, ClientDetails client) Description copied from interface:ChainedProxyManagerAdds the proxies to try, in order, tochainedProxies. Leave the queue empty to reject the request with502, or addChainedProxyAdapter.FALLBACK_TO_DIRECT_CONNECTIONto connect directly.- Specified by:
lookupChainedProxiesin interfaceChainedProxyManager- Parameters:
request- the request headchainedProxies- the queue to populate with proxies to try, in orderclient- the client address and authentication details
-