Web Service - JSON-RPC

Content:

  1. Introduction
  2. JSON-RPC
  3. Example
  4. Server
  5. Client
  6. Conclusion

Introduction:

Web services are common in most applications today.

Web services provide synchroneous request-response style integration between applications in a convenient way:

Any developer need to have a basic understanding of web services.

JSON-RPC:

Web services exist in two types:

RPC style
RPC (Remote Procedure Call) style mean that the web service expose operations with arguments and return values.
RESTful style
REST (Representational state transfer) style mean that the web service expose CRUD for resources.

This article will cover RPC style JSON-RPC web services.

For RPC style SOAP web services read here.

For RESTful style web services read here.

If asynchroneous integration is required then look at message queues. Read more here.

JSON-RPC is a RPC style web service standard.

It is very much inspired by XML-RPC, but as the name indicates then it uses JSON as format instead of XML.

Version 1.0 was first released in 2005, but it was first truly standardized with version 2.0 in 2009/2010.

JSON-RPC has never been widely used. RESTful web services (usually with JSON as format) is by far the most common.

But the standard still exists and there are good libraries for most languages. So if one need/prefer RPC style, then JSON-RPC is an interesting possibility.

Example:

We will look at an example with the following calls:

The focus will not on a realtsic example but more about showing how to get data and get data out. As this is the real issue in some libraries.

The servers will be embedded in application - not deployed on general web server. As I believe that is the most common scenario for JSON-RPC.

The most common client language is probbaly JavaScript, but due to my lack of JavaScript skills, then I will not show examples of JavaScript client.

Server:

Data.java:

package common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}

Test.java:

package server;

import java.util.ArrayList;
import java.util.List;

import common.Data;

public class Test {
    public int getInt() {
        return 123;
    }
    public String getString() {
        return "ABC";
    }
    public Data getData() {
        return new Data(123, "ABC");
    }
    public List<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    public List<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    public List<Data> getListOfData() {
        ArrayList<Data> res = new ArrayList<Data>();
        res.add(new Data(123, "ABC"));
        res.add(new Data(456, "DEF"));
        return res;
    }
    public int add(int a, int b) {
        return a + b;
    }
    public String concat(String a, String b) {
        return a + b;
    }
    public Data modify(Data o) {
        Data o2 = new Data(o.getIval() + 1, o.getSval() + "X");
        return o2;
    }
}

TestServlet.java:

package server;

import java.io.IOException;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.googlecode.jsonrpc4j.JsonRpcServer;

@WebServlet(urlPatterns={"/test"})
public class TestServlet extends HttpServlet {
    private JsonRpcServer real = new JsonRpcServer(new Test());
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        real.handle(req, resp);
    }
}

TestServer.java:

package server;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class TestServer {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8001);
        ServletContextHandler ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        ctx.setContextPath("/");
        server.setHandler(ctx);
        ServletHolder srvlet = ctx.addServlet(TestServlet.class, "/*");
        server.start();
        server.join();
    }
}

Data.java:

package common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}

Test.java:

package server;

import java.util.ArrayList;
import java.util.List;

import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcMethod;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcParam;
import com.github.arteam.simplejsonrpc.core.annotation.JsonRpcService;

import common.Data;

@JsonRpcService
public class Test {
    @JsonRpcMethod
    public int getInt() {
        return 123;
    }
    @JsonRpcMethod
    public String getString() {
        return "ABC";
    }
    @JsonRpcMethod
    public Data getData() {
        return new Data(123, "ABC");
    }
    @JsonRpcMethod
    public List<Integer> getListOfInts() {
        ArrayList<Integer> res = new ArrayList<Integer>();
        res.add(123);
        res.add(456);
        return res;
    }
    @JsonRpcMethod
    public List<String> getListOfStrings() {
        ArrayList<String> res = new ArrayList<String>();
        res.add("ABC");
        res.add("DEF");
        return res;
    }
    @JsonRpcMethod
    public List<Data> getListOfData() {
        ArrayList<Data> res = new ArrayList<Data>();
        res.add(new Data(123, "ABC"));
        res.add(new Data(456, "DEF"));
        return res;
    }
    @JsonRpcMethod
    public int add(@JsonRpcParam("a")int a, @JsonRpcParam("b")int b) {
        return a + b;
    }
    @JsonRpcMethod
    public String concat(@JsonRpcParam("s1")String a, @JsonRpcParam("s2")String b) {
        return a + b;
    }
    @JsonRpcMethod
    public Data modify(@JsonRpcParam("o")Data o) {
        Data o2 = new Data(o.getIval() + 1, o.getSval() + "X");
        return o2;
    }
}

TestServlet.java:

package server;

import java.io.IOException;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.github.arteam.simplejsonrpc.server.JsonRpcServer;

@WebServlet(urlPatterns={"/test"})
public class TestServlet extends HttpServlet {
    private JsonRpcServer real = new JsonRpcServer();
    private Test svc = new Test();
    public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        real.handle(req.getInputStream(), resp.getOutputStream(), svc);
    }
}

TestServer.java:

package server;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

public class TestServer {
    public static void main(String[] args) throws Exception {
        Server server = new Server(8002);
        ServletContextHandler ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
        ctx.setContextPath("/");
        server.setHandler(ctx);
        ServletHolder srvlet = ctx.addServlet(TestServlet.class, "/*");
        server.start();
        server.join();
    }
}

Data.groovy:

package common

class Data {
    int ival
    String sval
    @Override
    String toString() {
        return String.format("(%d,%s)", ival, sval)
    }
}

TestServer.groovy:

package server

import javax.servlet.annotation.*
import javax.servlet.http.*

import org.eclipse.jetty.server.*
import org.eclipse.jetty.servlet.*

import com.googlecode.jsonrpc4j.*

import common.*

class Test {
    int getInt() {
        return 123
    }
    String getString() {
        return "ABC"
    }
    Data getData() {
        return new Data(ival: 123, sval: "ABC")
    }
    List<Integer> getListOfInts() {
        return [ 123, 456 ]
    }
    List<String> getListOfStrings() {
        return [ "ABC", "DEF" ]
    }
    List<Data> getListOfData() {
        return [ new Data(ival: 123, sval: "ABC"), new Data(ival: 456, sval: "DEF") ]
    }
    int add(int a, int b) {
        return a + b
    }
    String concat(String a, String b) {
        return a + b
    }
    Data modify(Data o) {
        return new Data(ival: o.ival + 1, sval: o.sval + "X")
    }
}

@WebServlet(urlPatterns=["/test"])
class TestServlet extends HttpServlet {
    def real = new JsonRpcServer(new Test())
    void doPost(HttpServletRequest req, HttpServletResponse resp) {
        real.handle(req, resp)
    }
}

server = new Server(8003)
ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
ctx.setContextPath("/")
server.setHandler(ctx)
srvlet = ctx.addServlet(TestServlet.class, "/*")
server.start()
server.join()

Data.groovy:

package common

class Data {
    int ival
    String sval
    @Override
    String toString() {
        return String.format("(%d,%s)", ival, sval)
    }
}

TestServer.groovy:

package server

import javax.servlet.annotation.*
import javax.servlet.http.*

import org.eclipse.jetty.server.*
import org.eclipse.jetty.servlet.*

import com.github.arteam.simplejsonrpc.core.annotation.*;
import com.github.arteam.simplejsonrpc.server.*

import common.*

@JsonRpcService
class Test {
    @JsonRpcMethod
    int getInt() {
        return 123
    }
    @JsonRpcMethod
    String getString() {
        return "ABC"
    }
    @JsonRpcMethod
    Data getData() {
        return new Data(ival: 123, sval: "ABC")
    }
    @JsonRpcMethod
    List<Integer> getListOfInts() {
        return [ 123, 456 ]
    }
    @JsonRpcMethod
    List<String> getListOfStrings() {
        return [ "ABC", "DEF" ]
    }
    @JsonRpcMethod
    List<Data> getListOfData() {
        return [ new Data(ival: 123, sval: "ABC"), new Data(ival: 456, sval: "DEF") ]
    }
    @JsonRpcMethod
    int add(@JsonRpcParam("a")int a, @JsonRpcParam("b")int b) {
        return a + b
    }
    @JsonRpcMethod
    String concat(@JsonRpcParam("a")String a, @JsonRpcParam("b")String b) {
        return a + b
    }
    @JsonRpcMethod
    Data modify(@JsonRpcParam("o")Data o) {
        return new Data(ival: o.ival + 1, sval: o.sval + "X")
    }
}

@WebServlet(urlPatterns=["/test"])
public class TestServlet extends HttpServlet {
    def real = new JsonRpcServer()
    def svc = new Test()
    void doPost(HttpServletRequest req, HttpServletResponse resp) {
        real.handle(req.inputStream, resp.outputStream, svc)
    }
}

server = new Server(8004)
ctx = new ServletContextHandler(ServletContextHandler.NO_SESSIONS)
ctx.setContextPath("/")
server.setHandler(ctx)
srvlet = ctx.addServlet(TestServlet.class, "/*")
server.start()
server.join()

server.py:

from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

from jsonrpc import JSONRPCResponseManager, dispatcher

def getInt():
    return 123

def getString():
    return 'ABC'

def getData():
    return { 'ival': 123, 'sval': 'ABC' }

def getListOfInts():
    return [ 123, 456 ]

def getListOfStrings():
    return [ 'ABC', 'DEF' ]

def getListOfData():
    return [ { 'ival': 123, 'sval': 'ABC' }, { 'ival': 456, 'sval': 'DEF' } ]

def add(v1, v2):
    return v1 + v2

def concat(s1, s2):
    return s1 + s2

def modify(d):
    return { 'ival': d['ival'] + 1, 'sval': d['sval'] + 'X' }

@Request.application
def application(request):
    dispatcher['getInt'] = getInt
    dispatcher['getString'] = getString
    dispatcher['getData'] = getData
    dispatcher['getListOfInts'] = getListOfInts
    dispatcher['getListOfStrings'] = getListOfStrings
    dispatcher['getListOfData'] = getListOfData
    dispatcher['add'] = add
    dispatcher['concat'] = concat
    dispatcher['modify'] = modify
    response = JSONRPCResponseManager.handle(request.data, dispatcher)
    return Response(response.json, mimetype='application/json')

run_simple('localhost', 8005, application)

server.php:

<?php

require 'vendor/autoload.php';

class Test {
    public function getInt() {
        return 123;
    }
    public function getString() {
        return 'ABC';
    }
    public function getData() {
        return [ 'ival' => 123, 'sval' => 'ABC' ];
    }
    public function getListOfInts() {
        return [ 123, 456 ];
    }
    public function getListOfStrings() {
        return [ 'ABC', 'DEF' ];
    }
    public function getListOfData() {
        return [ [ 'ival' => 123, 'sval' => 'ABC' ], [ 'ival' => 456, 'sval' => 'DEF' ] ];
    }
    public function add($v1, $v2) {
        return $v1 + $v2;
    }
    public function concat($s1, $s2) {
        return $s1 . $s2;
    }
    public function modify($d) {
        return [ 'ival' => $d['ival'] + 1, 'sval' => $d['sval'] . 'X' ];
    }
}

use JsonRPC\Server;

$server = new Server();
$server->getProcedureHandler()->withObject(new Test());
echo $server->execute();

?>

server.php:

<?php

require 'vendor/autoload.php';

class Test {
    public function getInt() {
        return 123;
    }
    public function getString() {
        return 'ABC';
    }
    public function getData() {
        return [ 'ival' => 123, 'sval' => 'ABC' ];
    }
    public function getListOfInts() {
        return [ 123, 456 ];
    }
    public function getListOfStrings() {
        return [ 'ABC', 'DEF' ];
    }
    public function getListOfData() {
        return [ [ 'ival' => 123, 'sval' => 'ABC' ], [ 'ival' => 456, 'sval' => 'DEF' ] ];
    }
    public function add($v1, $v2) {
        return $v1 + $v2;
    }
    public function concat($s1, $s2) {
        return $s1 . $s2;
    }
    public function modify($d) {
        return [ 'ival' => $d['ival'] + 1, 'sval' => $d['sval'] . 'X' ];
    }
}

use PhpJsonRpc\Server\MapperInterface;
use PhpJsonRpc\Server;

class MyMapper implements MapperInterface {
    public function getClassAndMethod(string $method): array {
        return [ Test::class, $method ];
    }
}

$server = new Server();
$server->setMapper(new MyMapper());
$server->addHandler(new Test());
echo $server->execute();

?>

Client:

Data.java:

package common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}

TestClient.java:

package client;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.List;

import com.googlecode.jsonrpc4j.JsonRpcHttpClient;

import common.Data;

public class TestClient {
    private static void test(String urlstr) throws Throwable {
        System.out.println(urlstr);
        JsonRpcHttpClient client = new JsonRpcHttpClient(new URL(urlstr));
        int v = client.invoke("getInt", new Object[0], Integer.class);
        System.out.println(v);
        String s = client.invoke("getString", new Object[0], String.class);
        System.out.println(s);
        Data d = client.invoke("getData", new Object[0], Data.class);
        System.out.println(d);
        @SuppressWarnings("unchecked")
        List<Integer> lstv = client.invoke("getListOfInts", new Object[0], List.class);
        System.out.println(lstv);
        @SuppressWarnings("unchecked")
        List<String> lsts = client.invoke("getListOfStrings", new Object[0], List.class);
        System.out.println(lsts);
        Type lstdata = new ParameterizedType() {
            @Override
            public Type[] getActualTypeArguments() {
                return new Type[] { Data.class };
            }
            @Override
            public Type getRawType() {
                return List.class;
            }
            @Override
            public Type getOwnerType() {
                return null;
            }
        };
        @SuppressWarnings("unchecked")
        List<Data> lstd = (List<Data>)client.invoke("getListOfData", new Object[0], lstdata);
        System.out.println(lstd);
        int v1 = 123;
        int v2 = 456;
        int v3 = client.invoke("add", new Object[] { v1,  v2 }, Integer.class);
        System.out.println(v3);
        String s1 = "ABC";
        String s2 = "DEF";
        String s3 = client.invoke("concat", new Object[] { s1, s2 }, String.class);
        System.out.println(s3);
        Data d1 = new Data(123, "ABC");
        Data d2 = client.invoke("modify", new Object[] { d1 }, Data.class);
        System.out.println(d2);
    }
    public static void main(String[] args) throws Throwable {
        test("http://localhost:8001/test");
        test("http://localhost:8002/test");
        test("http://localhost:8003/test");
        test("http://localhost:8004/test");
        test("http://localhost:8005/test");
        test("http://localhost:8006/server.php");
        test("http://localhost:8007/server.php");
    }
}

Data.java:

package common;

public class Data {
    private int ival;
    private String sval;
    public Data() {
        this(0, "");
    }
    public Data(int ival, String sval) {
        super();
        this.ival = ival;
        this.sval = sval;
    }
    public int getIval() {
        return ival;
    }
    public void setIval(int ival) {
        this.ival = ival;
    }
    public String getSval() {
        return sval;
    }
    public void setSval(String sval) {
        this.sval = sval;
    }
    @Override
    public String toString() {
        return String.format("(%d,%s)", ival, sval);
    }
}

TestClient.java:

package client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;

import com.github.arteam.simplejsonrpc.client.JsonRpcClient;
import com.github.arteam.simplejsonrpc.client.Transport;

import common.Data;

public class TestClient {
    public static class MyTransport2 implements Transport {
        private String urlstr;
        public MyTransport2(String urlstr) {
            this.urlstr = urlstr;
        }
        @Override
        public String pass(String body) throws IOException {
            HttpClient client = HttpClientBuilder.create().build();
            HttpPost request = new HttpPost(urlstr); 
            request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON));
            HttpResponse response = client.execute(request);
            StringBuilder sb = new StringBuilder();
            BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
            String line;
            while((line = br.readLine()) != null) {
                sb.append(line);
            }
            br.close();
            return sb.toString();
        }
    }
    public static class MyTransport implements Transport {
        private String urlstr;
        public MyTransport(String urlstr) {
            this.urlstr = urlstr;
        }
        @Override
        public String pass(String body) throws IOException {
            try {
                HttpURLConnection con = (HttpURLConnection)(new URL(urlstr)).openConnection();
                con.setRequestMethod("POST");
                con.addRequestProperty("accept",  "application/json-rpc");
                con.addRequestProperty("content-type", "application/json-rpc");
                con.setDoOutput(true);
                con.getOutputStream().write(body.getBytes());
                StringBuilder sb = new StringBuilder();
                con.connect();
                if(con.getResponseCode() / 100 == 2) {
                    InputStream is = con.getInputStream();
                    byte[] b = new byte[1000];
                    int n;
                    while((n = is.read(b)) >= 0) {
                        sb.append(new String(b, 0, n));
                    }
                    is.close();
                } else {
                    System.out.printf("Error: %d %s\n", con.getResponseCode(), con.getResponseMessage());
                }
                con.disconnect();
                return sb.toString();
            } catch (MalformedURLException e) {
                e.printStackTrace();
                return null;
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    private static void test(String urlstr) {
        System.out.println(urlstr);
        JsonRpcClient client = new JsonRpcClient(new MyTransport(urlstr));
        int v = client.createRequest().method("getInt").id(1).returnAs(int.class).execute();
        System.out.println(v);
        String s = client.createRequest().method("getString").id(2).returnAs(String.class).execute();
        System.out.println(s);
        Data d = client.createRequest().method("getData").id(3).returnAs(Data.class).execute();
        System.out.println(d);
        @SuppressWarnings("unchecked")
        List<Integer> lstv = client.createRequest().method("getListOfInts").id(4).returnAs(List.class).execute();
        System.out.println(lstv);
        @SuppressWarnings("unchecked")
        List<String> lsts = client.createRequest().method("getListOfStrings").id(5).returnAs(List.class).execute();
        System.out.println(lsts);
        @SuppressWarnings("unchecked")
        List<Data> lstd = client.createRequest().method("getListOfData").id(6).returnAs(List.class).execute();
        System.out.println(lstd);
        int v1 = 123;
        int v2 = 456;
        int v3 = client.createRequest().method("add").params(v1, v2).id(7).returnAs(int.class).execute();
        System.out.println(v3);
        String s1 = "ABC";
        String s2 = "DEF";
        String s3 = client.createRequest().method("concat").params(s1, s2).id(8).returnAs(String.class).execute();
        System.out.println(s3);
        Data d1 = new Data(123, "ABC");
        Data d2 = client.createRequest().method("modify").params(d1).id(9).returnAs(Data.class).execute();
        System.out.println(d2);
    }
    public static void main(String[] args) {
        test("http://localhost:8001/test");
        test("http://localhost:8002/test");
        test("http://localhost:8003/test");
        test("http://localhost:8004/test");
        test("http://localhost:8005/test");
        test("http://localhost:8006/server.php");
        test("http://localhost:8007/server.php");
    }
}

Data.groovy:

package common

class Data {
    int ival
    String sval
    @Override
    String toString() {
        return String.format("(%d,%s)", ival, sval)
    }
}

TestClient.groovy:

package client

import java.lang.reflect.*

import com.googlecode.jsonrpc4j.*

import common.*

def test(String urlstr) {
    println(urlstr)
    client = new JsonRpcHttpClient(new URL(urlstr))
    v = client.invoke("getInt", new Object[0], Integer.class)
    println(v)
    s = client.invoke("getString", new Object[0], String.class)
    println(s)
    d = client.invoke("getData", new Object[0], Data.class)
    println(d)
    lstv = client.invoke("getListOfInts", new Object[0], List.class)
    println(lstv)
    lsts = client.invoke("getListOfStrings", new Object[0], List.class)
    println(lsts)
    lstdata = new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            return [ Data.class ]
        }
        @Override
        public Type getRawType() {
            return List.class
        }
        @Override
        public Type getOwnerType() {
            return null
        }
    }
    lstd = (List<Data>)client.invoke("getListOfData", new Object[0], lstdata)
    println(lstd)
    v1 = 123
    v2 = 456
    v3 = client.invoke("add", [ v1,  v2 ], Integer.class)
    println(v3)
    s1 = "ABC"
    s2 = "DEF"
    s3 = client.invoke("concat", [ s1, s2 ], String.class)
    println(s3)
    d1 = new Data(ival: 123, sval: "ABC")
    d2 = client.invoke("modify", [ d1 ], Data.class)
    println(d2)
}

test("http://localhost:8001/test")
test("http://localhost:8002/test")
test("http://localhost:8003/test")
test("http://localhost:8004/test")
test("http://localhost:8005/test")
test("http://localhost:8006/server.php")
test("http://localhost:8007/server.php")

Data.groovy:

package common

class Data {
    int ival
    String sval
    @Override
    String toString() {
        return String.format("(%d,%s)", ival, sval)
    }
}

TestClient.groovy:

package client

import java.lang.reflect.*

import org.apache.http.*
import org.apache.http.client.*
import org.apache.http.client.methods.*
import org.apache.http.entity.*
import org.apache.http.impl.client.*

import com.github.arteam.simplejsonrpc.client.*

import common.*

class MyTransport2 implements Transport {
    String urlstr
    @Override
    String pass(String body) {
        def client = HttpClientBuilder.create().build()
        def request = new HttpPost(urlstr) 
        request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON))
        def response = client.execute(request)
        def sb = new StringBuilder()
        def br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()))
        def line
        while((line = br.readLine()) != null) {
            sb.append(line)
        }
        br.close()
        return sb.toString()
    }
}

class MyTransport implements Transport {
    String urlstr
    @Override
    String pass(String body) {
        def con = (HttpURLConnection)(new URL(urlstr)).openConnection()
        con.setRequestMethod("POST")
        con.addRequestProperty("accept",  "application/json-rpc")
        con.addRequestProperty("content-type", "application/json-rpc")
        con.setDoOutput(true)
        con.getOutputStream().write(body.getBytes())
        def sb = new StringBuilder()
        con.connect()
        if(con.getResponseCode() / 100 == 2) {
            def is = con.getInputStream()
            def b = new byte[1000]
            def n
            while((n = is.read(b)) >= 0) {
                sb.append(new String(b, 0, n))
            }
            is.close()
        } else {
            printf("Error: %d %s\n", con.rResponseCode, con.responseMessage)
        }
        con.disconnect()
        return sb.toString()
    }
}

def test(String urlstr) {
    println(urlstr)
    client = new JsonRpcClient(new MyTransport(urlstr: urlstr))
    v = client.createRequest().method("getInt").id(1).returnAs(int.class).execute()
    println(v)
    s = client.createRequest().method("getString").id(2).returnAs(String.class).execute()
    println(s)
    d = client.createRequest().method("getData").id(3).returnAs(Data.class).execute()
    println(d)
    lstv = client.createRequest().method("getListOfInts").id(4).returnAs(List.class).execute()
    println(lstv)
    lsts = client.createRequest().method("getListOfStrings").id(5).returnAs(List.class).execute()
    println(lsts)
    lstd = client.createRequest().method("getListOfData").id(6).returnAs(List.class).execute()
    println(lstd)
    v1 = 123
    v2 = 456
    v3 = client.createRequest().method("add").params(v1, v2).id(7).returnAs(int.class).execute()
    println(v3)
    s1 = "ABC"
    s2 = "DEF"
    s3 = client.createRequest().method("concat").params(s1, s2).id(8).returnAs(String.class).execute()
    println(s3)
    d1 = new Data(ival: 123, sval: "ABC")
    d2 = client.createRequest().method("modify").params(d1).id(9).returnAs(Data.class).execute()
    println(d2)
}

test("http://localhost:8001/test")
test("http://localhost:8002/test")
test("http://localhost:8003/test")
test("http://localhost:8004/test")
test("http://localhost:8005/test")
test("http://localhost:8006/server.php")
test("http://localhost:8007/server.php")

client.py:

import requests
import json

def invoke(urlstr, method, params):
    req = { 'method': method, 'params': params, 'jsonrpc': '2.0', 'id': 0, }
    return requests.post(urlstr, json=req).json()['result']

def test(urlstr):
    print(urlstr)
    v = invoke(urlstr, 'getInt', [])
    print(v)
    s = invoke(urlstr, 'getString', [])
    print(s)
    d = invoke(urlstr, 'getData', [])
    print(d)
    lstv = invoke(urlstr, 'getListOfInts', [])
    print(lstv)
    lsts = invoke(urlstr, 'getListOfStrings', [])
    print(lsts)
    lstd = invoke(urlstr, 'getListOfData', [])
    print(lstd)
    v1 = 123
    v2 = 456
    v3 = invoke(urlstr, 'add', [ v1, v2 ])
    print(v3)
    s1 = 'ABC'
    s2 = 'DEF'
    s3 = invoke(urlstr, 'concat', [ s1, s2 ])
    print(s3)
    d1 = { 'ival': 123, 'sval': 'ABC' }
    d2 = invoke(urlstr, 'modify', [ d1 ])
    print(d2)

test('http://localhost:8001/test')
test('http://localhost:8002/test')
test('http://localhost:8003/test')
test('http://localhost:8004/test')
test('http://localhost:8005/test')
test('http://localhost:8006/server.php')
test('http://localhost:8007/server.php')

client.php:

<?php

require 'vendor/autoload.php';

use JsonRPC\Client;

function data2string($d) {
    return "({$d['ival']},{$d['sval']})";
}

function test($urlstr) {
    echo "$urlstr\r\n";
    $client = new Client($urlstr);
    $v = $client->execute('getInt', []);
    echo "$v\r\n";
    $s = $client->execute('getString', []);
    echo "$s\r\n";
    $d = $client->execute('getData', []);
    $temp = data2string($d);
    echo "$temp\r\n";
    $lstv = $client->execute('getListOfInts', []);
    $temp = '[' . implode(',',$lstv) . ']';
    echo "$temp\r\n";
    $lsts = $client->execute('getListOfStrings', []);
    $temp = '[' . implode(',',$lsts) . ']';
    echo "$temp\r\n";
    $lstd = $client->execute('getListOfData', []);
    $temp = '[' . implode(',',array_map(function($d) { return data2string($d); }, $lstd)) . ']';
    echo "$temp\r\n";
    $v1 = 123;
    $v2 = 456;
    $v3 = $client->execute('add', [$v1, $v2]);
    echo "$v3\r\n";
    $s1 = 'ABC';
    $s2 = 'DEF';
    $s3 = $client->execute('concat', [$s1, $s2]);
    echo "$s3\r\n";
    $d1 = [ 'ival' => 123, 'sval' => 'ABC' ];
    $d2 = $client->execute('modify', [ $d1 ]);
    $temp = data2string($d2);
    echo "$temp\r\n";
}

test('http://localhost:8001/test');
test('http://localhost:8002/test');
test('http://localhost:8003/test');
test('http://localhost:8004/test');
test('http://localhost:8005/test');
test('http://localhost:8006/server.php');
test('http://localhost:8007/server.php');

?>

client.php:

<?php

require 'vendor/autoload.php';

use PhpJsonRpc\Client;

function data2string($d) {
    return "({$d['ival']},{$d['sval']})";
}

function test($urlstr) {
    echo "$urlstr\r\n";
    $client = new Client($urlstr);
    $v = $client->call('getInt', []);
    echo "$v\r\n";
    $s = $client->call('getString', []);
    echo "$s\r\n";
    $d = $client->call('getData', []);
    $temp = data2string($d);
    echo "$temp\r\n";
    $lstv = $client->call('getListOfInts', []);
    $temp = '[' . implode(',',$lstv) . ']';
    echo "$temp\r\n";
    $lsts = $client->call('getListOfStrings', []);
    $temp = '[' . implode(',',$lsts) . ']';
    echo "$temp\r\n";
    $lstd = $client->call('getListOfData', []);
    $temp = '[' . implode(',',array_map(function($d) { return data2string($d); }, $lstd)) . ']';
    echo "$temp\r\n";
    $v1 = 123;
    $v2 = 456;
    $v3 = $client->call('add', [$v1, $v2]);
    echo "$v3\r\n";
    $s1 = 'ABC';
    $s2 = 'DEF';
    $s3 = $client->call('concat', [$s1, $s2]);
    echo "$s3\r\n";
    $d1 = [ 'ival' => 123, 'sval' => 'ABC' ];
    $d2 = $client->call('modify', [ $d1 ]);
    $temp = data2string($d2);
    echo "$temp\r\n";
}

test('http://localhost:8001/test');
test('http://localhost:8002/test');
test('http://localhost:8003/test');
test('http://localhost:8004/test');
test('http://localhost:8005/test');
test('http://localhost:8006/server.php');
test('http://localhost:8007/server.php');

?>

Conclusion:

It works and it has a very good level of compatibility.

Article history:

Version Date Description
1.0 May 8th 2024 Initial version

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj