Apex http builder utility, with proxy

I’ve recently implemented an HTTP builder class that I thought I would share. This makes sending HTTP requests super simple and the calling code easy to read (see below)…

public class HttpCalloutBuilder {

    private String endpoint;
    private HttpMethod method;
    private Map<String, String> parameters;
    private Map<String, String> headers;
    private String body;
    private String clientCertName;
    private String clientCert;
    private String password;
    private IHttpProxy proxy;
    private Log.IntegrationLog iLog;

    public enum HttpMethod {
        HttpGet,
        HttpPost,
        HttpPut,
        HttpPatch,
        HttpDelete
    }

    public HttpCalloutBuilder() {
        this(new HttpProxy());
    }

    public HttpCalloutBuilder(IHttpProxy proxy){
        this.parameters = new Map<String, String>();
        this.headers = new Map<String, String>();
        this.proxy = proxy;
        this.method = HttpMethod.HttpGet;
    }

    public HttpCalloutBuilder withEndpoint(string endpoint) {
        this.endpoint = endpoint;
        return this;
    }

    public HttpCalloutBuilder withMethod(HttpMethod method){
        this.method = method;
        return this;
    }

    public HttpCalloutBuilder addParameter(string key, string value) {
        this.parameters.put(key, value);
        return this;
    }

    public HttpCalloutBuilder addParameters(Map<String, String> parameters){
        this.parameters.putAll(parameters);
        return this;
    }

    public HttpCalloutBuilder addHeader(string key, string value) {
        this.headers.put(key, value);
        return this;
    }

    public HttpCalloutBuilder addHeaders(Map<String, String> headers){
        this.headers.putAll(headers);
        return this;
    }

    public HttpCalloutBuilder withBody(string body){
        this.body = body;
        return this;
    }

    public HttpCalloutBuilder withBasicAuthentication(string accessToken){
        this.headers.put('Authorization', 'Basic ' + accessToken);
        return this;
    }

    public HttpCalloutBuilder withBearerToken(string accessToken){
        this.headers.put('Authorization', 'Bearer ' + accessToken);
        return this;
    }

    public HttpCalloutBuilder withContentType(string contentType){
        this.headers.put('Content-Type', contentType);
        return this;
    }

    public HttpCalloutBuilder withAccept(string accept){
        this.headers.put('Accept', accept);
        return this;
    }

    public HttpCalloutBuilder withContentTypeFormUrlEncoded() {
        return this.withContentType('application/x-www-form-urlencoded');
    }

    public HttpCalloutBuilder withClientCertificateName(String clientCertName){
        this.clientCertName = clientCertName;
        return this;
    }

    public HttpCalloutBuilder withClientCertificate(String clientCert, String password){
        this.clientCert = clientCert;
        this.password = password;
        return this;
    }

    public HttpResponse send() {
        HttpRequest request = new HttpRequest();

        switch on this.method {
            when HttpPost {
                request.setMethod('POST');
            }
            when HttpPut {
                request.setMethod('PUT');
            }
            when HttpPatch {
                request.setMethod('PATCH');
            }
            when HttpDelete {
                request.setMethod('DELETE');
            }
            when else {
                request.setMethod('GET');
            }
        }

        if (this.body != null) {
            request.setBody(this.body);
        }

        for (string key : this.headers.keySet()){
            request.setHeader(key, this.headers.get(key));
        }

        string endpointWithParameters = this.endpoint;
        if (!this.parameters.isEmpty()){
            endpointWithParameters += '?';
            for (string key : this.parameters.keySet()){
                endpointWithParameters += '&' + key + '=' + this.parameters.get(key);
            }
        }

        if (endpointWithParameters != null){
            request.setEndpoint(endpointWithParameters);
        }


        if (!String.isEmpty(clientCertName)){
            request.setClientCertificateName(clientCertName);
        }

        if (!String.isEmpty(clientCert) && !String.isEmpty(password)){
            request.setClientCertificate(clientCert, password);
        }

        HttpResponse response;
        try{
            response = proxy.send(request);
        } catch (Exception ex) {
            // TODO: add your custom error handling and logging here
            throw ex;
        } 

        return response;
    }
}



public interface IHttpProxy {

    HttpResponse send(HttpRequest request);
}



public class HttpProxy implements IHttpProxy {

    public HttpResponse send(HttpRequest request) {
        HttpResponse response = new Http().send(request);
        return response;
    }
}

This makes sending a callout as easy (and readable) as…

HttpCalloutBuilder builder = new HttpCalloutBuilder()
        .withEndpoint('http://some.endpoint.address')
        .withBearerToken('sometokenvalue')
        .withMethod(HttpCalloutBuilder.HttpMethod.HttpPost)
        .withContentTypeFormUrlEncoded()
        .withBody('somebodyvalue'));
HttpResponse response = builder.send();

For our implementation, we have our custom logging solution weaved into the builder as well so we log callouts and also include logic so we don’t log the authorization parameters / response.

It was however that the implementation of the proxy that proved most useful when I uncovered a bug in one of the services we were calling out to. The request and response had JSON formatted differently (different capitalization on field names across the request and response and the use of field names that we couldn’t create as a value object properties for JSON serialization in Salesforce).

By creating a custom IHttpProxy class, I was able to transform the request and response body directly before sending and immediately after receiving without impact any of the main logic for our solution.

public class HttpProxyFieldNameFormatter implements IHttpProxy {

    private Map<String, String> requestReplacements = new Map<String, String>{
            '"firstName"' => '"first__name"',
            '"lastName"' => '"last__name"',
            '"orgName"' => '"org__name"'
    };

    public HttpResponse send(HttpRequest request) {
        String requestBody = formatRequest(request.getBody());
        request.setBody(requestBody);
        HttpResponse response = new Http().send(request);

        String responseBody = response.getBody();
        response.setBody(formatResponse(responseBody));

        return response;
    }

    public String formatRequest(String requestBody){
        string body = requestBody;
        for (String key : requestReplacements.keySet()){
            body = body.replace(key, requestReplacements.get(key));
        }

        return body;
    }

    public String formatResponse(String responseBody){
        string body = responseBody;
        for (string key : requestReplacements.keySet()){
            body = body.replace(requestReplacements.get(key), key);
        }

        return body;
    }

}

Leave a Comment

Your email address will not be published. Required fields are marked *