Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic HTTP Client #297

Open
winder opened this issue Aug 26, 2022 · 0 comments
Open

Basic HTTP Client #297

winder opened this issue Aug 26, 2022 · 0 comments

Comments

@winder
Copy link

winder commented Aug 26, 2022

I found that the JsonRpcHttpClient did not work with an RPC server provided by gorilla/rpc. The problem seems to be that HttpUrlConnection is performing a CONNECT while this server is configured to use POST, and therefore gorilla/rpc simply returns a 405.

Firstly, is there a way to configure JsonRpcHttpClient to use simple requests instead of a connect? I think the answer is no, and thus the basis of a similar issue #252.

Secondly, if JsonRpcHttpClient cannot work with gorilla/rpc, here is what I came up with to work around the limitation. This is not quite production code, but it is a very minimal adapter that combines the JsonRpcClient and (an older version of) OkHttp to fully implement IJsonRpcClient:

package com.path.to.package;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.jsonrpc4j.IJsonRpcClient;
import com.googlecode.jsonrpc4j.JsonRpcClient;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;


import java.io.ByteArrayOutputStream;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import static com.googlecode.jsonrpc4j.JsonRpcBasicServer.ACCEPT_ENCODING;

public class RpcServerOkHttpClient extends JsonRpcClient implements IJsonRpcClient {
    private final OkHttpClient client = new OkHttpClient();
    private final MediaType mediaType = MediaType.parse("application/json");

    private static final String GZIP = "gzip";

    private final Map<String, String> headers = new HashMap<>();
    private URL serviceUrl;
    private boolean gzipRequests = false;

    /**
     * Creates the {@link RpcServerOkHttpClient} bound to the given {@code serviceUrl}.
     * The headers provided in the {@code headers} map are added to every request
     * made to the {@code serviceUrl}.
     *
     * @param serviceUrl the service end-point URL
     * @param headers    the headers
     */
    public RpcServerOkHttpClient(URL serviceUrl, Map<String, String> headers) {
        this(new ObjectMapper(), serviceUrl, headers);
    }

    /**
     * Creates the {@link RpcServerOkHttpClient} bound to the given {@code serviceUrl}.
     * The headers provided in the {@code headers} map are added to every request
     * made to the {@code serviceUrl}.
     *
     * @param mapper     the {@link ObjectMapper} to use for json&lt;-&gt;java conversion
     * @param serviceUrl the service end-point URL
     * @param headers    the headers
     */
    public RpcServerOkHttpClient(ObjectMapper mapper, URL serviceUrl, Map<String, String> headers) {
        this(mapper, serviceUrl, headers, false, false);
    }

    /**
     * Creates the {@link RpcServerOkHttpClient} bound to the given {@code serviceUrl}.
     * The headers provided in the {@code headers} map are added to every request
     * made to the {@code serviceUrl}.
     *
     * @param mapper              the {@link ObjectMapper} to use for json&lt;-&gt;java conversion
     * @param serviceUrl          the service end-point URL
     * @param headers             the headers
     * @param gzipRequests        whether gzip the request
     * @param acceptGzipResponses whether accept gzip response
     */
    public RpcServerOkHttpClient(ObjectMapper mapper, URL serviceUrl, Map<String, String> headers, boolean gzipRequests, boolean acceptGzipResponses) {
        super(mapper);
        this.serviceUrl = serviceUrl;
        this.headers.putAll(headers);
        this.gzipRequests = gzipRequests;
        if (acceptGzipResponses) {
            this.headers.put(ACCEPT_ENCODING, GZIP);
        }
    }

    /**
     * Creates the {@link RpcServerOkHttpClient} bound to the given {@code serviceUrl}.
     * The headers provided in the {@code headers} map are added to every request
     * made to the {@code serviceUrl}.
     *
     * @param serviceUrl the service end-point URL
     */
    public RpcServerOkHttpClient(URL serviceUrl) {
        this(new ObjectMapper(), serviceUrl, new HashMap<String, String>());
    }

    /**
     * {@inheritDoc}
     */

    @Override
    public void invoke(String methodName, Object argument) throws Throwable {
        invoke(methodName, argument, null, new HashMap<>());
    }

    @Override
    public Object invoke(String methodName, Object argument, Type returnType) throws Throwable {
        return invoke(methodName, argument, returnType, new HashMap<>());
    }

    @Override
    public Object invoke(String methodName, Object argument, Type returnType, Map<String, String> extraHeaders) throws Throwable {
        var baos = new ByteArrayOutputStream();
        super.invoke(methodName, argument, baos);

        var body = RequestBody.create(mediaType, baos.toByteArray());
        var request = new Request.Builder()
                .url(serviceUrl)
                .post(body)
                .headers(Headers.of(extraHeaders))
                .build();

        if (gzipRequests) {
            // TODO: set something on okhttp
        }

        var call = client.newCall(request);
        var response = call.execute();

        if (! response.isSuccessful()) {
            throw new RuntimeException("Failed to execute request: " + response);
        }

        return super.readResponse(returnType, response.body().byteStream());
    }

    @Override
    public <T> T invoke(String methodName, Object argument, Class<T> clazz) throws Throwable {
        return invoke(methodName, argument, clazz, new HashMap<>());
    }

    @Override
    public <T> T invoke(String methodName, Object argument, Class<T> clazz, Map<String, String> extraHeaders) throws Throwable {
        return (T) invoke(methodName, argument, (Type) clazz, new HashMap<>());
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant