Skip to content
Daniyaal Khan edited this page Oct 8, 2024 · 15 revisions

Welcome to the Degressly wiki!

Contents

Advanced Configurations Supported in Degressly

Intercepting SSL Communication in degressly-downstream

Since degressly works as an HTTP Proxy, intercepting SSL requests takes some good ol' man in the middle interception.

We recommend using mitmproxy as the HTTPS_PROXY to intercept the https requests and forward it to degressly-downstream as an HTTP request. A sample config for mitmproxy follows:

from mitmproxy import http

def request(flow: http.HTTPFlow) -> None:
    original_host = flow.request.host_header


    flow.request.host = 'degressly-downstream'
    flow.request.port = 8080
    flow.request.scheme = "http"

    # Change the Host header to the original destination
    if original_host:
        flow.request.host_header = original_host

    # Optionally, log or debug the redirected flow
    print(f"Redirecting HTTPS request to HTTP: {flow.request.url}")

    flow.request.headers["X-Forwarded-Proto"] = "https"

You will have to provide mitmproxy with a Root CA certificate or let mitmproxy generate one of its own, and that certificate then must be added to the trust store of your docker containers.

Refer to the mitmproxy certificate authority documentation for more details regarding certificate setup.

For example, the following directives can be added in Ubuntu/Debian based images for adding trusted certificates:

...
COPY ./certs/mitmproxy-ca-cert.crt /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
RUN update-ca-certificates
...

As an addendum, some runtimes like python and JVM do not use the OS trusted root CAs for their trust store. In such cases, you may have to manually configure the certificates. For example, if you are using the python requests library you can add the following directive to your Dockerfile:

...
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
...

A working example for easy reference is present in degressly/degressly-demo with the MITM interceptor.


Custom Config for populating Trace ID / Idempotency Handling in degressly-downstream

degressly-downstream supports Groovy based configuration files for use cases such as:

  • When your application calls a wide array of upstream services, some idempotent and some not.
  • When changes cannot be made to the code for obtaining the idempotency keys or trace IDs of requests.

A groovy script that configuration has to implement the methods of com.degressly.proxy.downstream.handler.DownstreamHandler.

A sample Groovy Config is as follows:

package config

import com.degressly.proxy.downstream.dto.RequestContext
import groovy.json.JsonSlurper
import org.springframework.util.MultiValueMap

class DownstreamHandler implements com.degressly.proxy.downstream.handler.DownstreamHandler {

    Set<String> idempotentURIs = Set.of("/sample-idempotent", );

    @Override
    Optional<Boolean> isIdempotent(RequestContext requestContext) {
        if (idempotentURIs.contains(requestContext.getRequest().getRequestURI())) {
            return Optional.of(Boolean.TRUE)
        } else {
            return Optional.of(Boolean.FALSE)
        }

    }

    @Override
    Optional<String> getTraceId(RequestContext requestContext) {

        JsonSlurper jsonSlurper = new JsonSlurper()
        def bodyJson

        try {
            bodyJson = jsonSlurper.parseText(requestContext.getBody())
        } catch(Exception ignored) {
            // Do nothing
            bodyJson = null
        }

        def optional

        optional = getField(requestContext.getHeaders(), requestContext.getParams(), bodyJson,"trace-id")
        if (optional.isPresent())
            return Optional.of(optional.get())

        optional = getField(requestContext.getHeaders(), requestContext.getParams(), bodyJson,"seqNo")
        if (optional.isPresent())
            return Optional.of(optional.get())

        optional = getField(requestContext.getHeaders(), requestContext.getParams(), bodyJson,"seq-no")
        if (optional.isPresent())
            return Optional.of((optional.get()))

        optional = getField(requestContext.getHeaders(), requestContext.getParams(), bodyJson,"txn-id")
        if (optional.isPresent())
            return Optional.of((optional.get()))

        optional = getField(requestContext.getHeaders(), requestContext.getParams(), bodyJson,"txnId")
        if (optional.isPresent())
            return Optional.of(optional.get())

        return Optional.empty()
    }

    @Override
    Optional<String> getIdempotencyKey(RequestContext requestContext) {
        if (requestContext.getTraceId()) {
            return Optional.of(requestContext.getRequest().getRequestURL().append("_").append(requestContext.getTraceId()).toString());
        }

        return Optional.empty();
    }

    private static Optional<String> getField(MultiValueMap<String, String> headers, MultiValueMap<String, String> params, Object bodyJson, String field) {
        if (headers.containsKey(field))
            return Optional.of(headers.getFirst(field))
        if (params.containsKey(field))
            return Optional.of(params.getFirst(field))
        if (bodyJson!=null && bodyJson[field] != null && bodyJson[field] instanceof String)
            return Optional.of((String) bodyJson[field])

        return Optional.empty()
    }
}

This config does the following:

  • declares the /sample-idempotent API as idempotent. i.e., each replica's calls will be forwarded to the upstream system. Whereas for all other APIs will be treated as non-idempotent.
  • extracts the traceId based on whichever field is found first in the request as per the order of precedence in the getTraceId method.
  • returns _ as the idempotency key.

Database seggregation without maintaining multiple copies of unchanged data

WIP

Clone this wiki locally