If you are a developer, and you have a project in PHP, and he finally needed to implement his own API - this article is definitely for you;).
JSON-RPC v1.0 appeared in 2005, 5 years later the
second version appeared . In the age of javascript and mobile applications, many developers still use their own bikes instead of a ready-made standard.
Why JSON-RPC, and yes even 2.0?
I will try to highlight key features:
- It is at least some kind of standard. A rich selection of libraries for various platforms.
- Fast easy parsing. JSON-RPC is not as monstrous as XML-RPC or SOAP.
- Built-in error handling. With REST bikes, you have to figure out how to build the answer and what to do with it further.
- Call Queuing Support. Now 5 HTTP requests can be wrapped into one :).
- Single point for API. For example, /rpc-server.php will be able to handle various methods.
- Support for named and optional parameters when calling methods.
For those familiar with version 1.0, significant innovations in 2.0 were named parameters and a call queue.
A simple request / response is as follows:
--> {"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3} <-- {"jsonrpc": "2.0", "result": 19, "id": 3} --> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1} <-- {"jsonrpc": "2.0", "result": 19, "id": 1} --> [ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"}, {"jsonrpc": "2.0", "method": "foobar", "id": "2"} ] <-- [ {"jsonrpc": "2.0", "result": 7, "id": "1"}, {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "2"} ]
In this complicated “creative” world of programming, the question always arises: to take something ready or write something of your own?
')
Perfect world. What do we need?
As usual, we define the criteria for the tool that I want to find / get:
- Full compliance with the specification. Given this factor, most likely, there will be no problems with other client / server solutions.
- Single-file independent implementation. I don't want to drag along some kind of framework a la Zend or PEAR
- Availability of tests. At least some guarantee that the open-source solution will work correctly;)
- Beautiful realization inside. Everything should be as simple as possible, understandable and logical.
- The presence of auto-discover mechanism. JSON-RPC Server should be able to give meta-information about its methods.
- Easy connection. I connected the file, called a couple of methods, everything works.
- Generate client proxy classes. Why write a client when we already have a ready server with metadata? ;)
Enough for a start.
A bit of theory
Because JSON-RPC is a fairly young protocol, there are still moments in it that are not fully approved. One of them is the
Service Mapping Description proposed by Dojo. SMD can fully describe a web service, starting from its methods, ending with expanded return types. Unfortunately, very few solutions support its implementation, for example, the
Zend_Json_Server , the inputEx framework (
generating the test forms ) and the
Dojo framework itself .
Let us turn to the search for existing solutions.
Existing PHP implementations
I took the list of clients from the
tablet in Wikipedia .
| php-json-rpc | jsonrpc2php | tivoka | junior | json-rpc-php | JSONRpc2 | Zend Json Server | zoServices |
Server |
Compliance specification | - | Proper support for Notification in Batch mode.
| + | No optional named params | + | + | + | No optional named params |
Number of files | - | 2 | > 7 | 3 | 6 | one | > 5 | 6 |
SMD diagram | - | - | - | - | - | - | + | - |
Tests | - | + | - | + | - | + | + | - |
Internal implementation (1..5) | - | four Manual mapping of exported functions | 3. Too difficult implementation for such a simple task. | four | 4. Difficult | 4. Magic Inside! | 4+. Zend | 3 |
Customer |
Compliance specification | No Batch and Notification | - | + | + | + | No batch | - | Notificaiton missing |
Number of files | one | - | > 7 | four | 2 | one | - | 6 |
Tests | - | - | - | + | - | + | - | - |
Internal implementation (1..5) | four | - | 3. Extra steps to call methods | four | four | 4+. :) | - | 3 |
Automatic generation | - | - | - | - | - | - | - | - |
A clever reader can argue, what a difference, how many files are used for implementation, you can still glue everything into one. Yes, it is possible, but this is an additional unnecessary action.
As we see, there is no “ideal” solution that would suit us and which can be used calmly without a file. I hope that on other platforms the situation is much better :)
There is a general feeling of the projects being left undone; no solution can offer a full cycle of using the exported API (server, smd-schema, client-generation).
I also do not really understand those developers who are trying to make PHP, say Java or C #. For the most part, PHP is used in the request / response scheme, and not in the application server scheme with its own states. The script is the same not compiled closed library.
The answer to the question “use something ready” or “write your own” is obvious.
Eazy JSON-RPC 2.0
Project on
github . All requirements, indicated earlier, are implemented :)
Server
There are two use options: either to inherit from the BaseJsonRpcServer class, or to create an instance of it and pass the exported object to the constructor:
<?php include 'BaseJsonRpcServer.php'; include 'tests/lib/DateTimeRpcService.php';
Thus, we have opened out all public methods of the DateTimeRpcService class. The SMD scheme can be obtained through the
smd GET parameter (for example,
eazyjsonrpc / example-server.php? Smd ). At construction of the scheme phpDoc-blocks are considered.
What is there for such a scheme ... {"transport":"POST","envelope":"JSON-RPC-2.0","SMDVersion":"2.0","contentType":"application\/json","target":"\/example-server.php","services":{"GetTime":{"parameters":[{"name":"timezone","optional":true,"type":"string","default":"UTC"},{"name":"format","optional":true,"type":"string","default":"c"}],"description":"Get Current Time","returns":{"type":"string"}},"GetTimeZones":{"parameters":[],"description":"Returns associative array containing dst, offset and the timezone name","returns":{"type":"array"}},"GetRelativeTime":{"parameters":[{"name":"text","optional":false,"type":"string","description":"a date\/time string\r"},{"name":"timezone","optional":true,"type":"string","default":"UTC"},{"name":"format","optional":true,"type":"string","default":"c"}],"description":"Get Relative time","returns":{"type":"string"}},"Implode":{"parameters":[{"name":"glue","optional":false,"type":"string"},{"name":"pieces","optional":true,"type":"array","default":["1","2","3"]}],"description":"Implode Function","returns":{"type":"string","description":"string"}}},"description":"Simple Date Time Service"}
Client
Again, there are two use cases: either create an instance of the BaseJsonRpcClient class and pass it the web service link in the constructor, or use the generator:
<?php include 'BaseJsonRpcClient.php'; $client = new BaseJsonRpcClient( 'http://eazyjsonrpc/example-server.php' ); $result = $client->GetRelativeTime( 'yesterday' );
Generator
Based on the SMD scheme, we can generate a class to work with the server (see the example of
DateTimeServiceClient.php ). To do this, call the generator:
php JsonRpcClientGenerator.php http://eazyjsonrpc/example-server.php?smd DateTimeServiceClient
The result of the command will be the DateTimeServiceClient.php file with the methods we need.
A spoon of tar
The unspoken rule for calling
class-> method () in JSON-RPC is using
class.method as the method name (through a dot).
In the current implementation of this functionality is not provided. It is assumed that the url is an exported class, then the option with dots disappears :). Regarding the client side, you can always add something here, it's just PHP.
It is also possible in SMD to describe the returned types as objects with their properties, but due to the complexity of the implementation, we will omit this moment.
For those who want to find detailed documentation, I can suggest reading the method names again, phpDoc comments for them, and the source code of the Server or Client.
Lifehacks
What we have with authentication?
There are several implementation options:
- Use HTTP Basic Auth. In the client, it is enough to add a login and password to the $ CurlOptions array :)
- Use tokens via HTTP headers. To obtain tokens, you can write the necessary method.
- Use tokens as method parameters.
How to deal with file uploads?
Some people offer a strange variant with encoding a file in base64 and sending it in some field.
A more or less normal solution is to implement a method that tells you where to start downloading a file.
Example --> {"jsonrpc": "2.0", "method": "send_image", "params": ..., "id": 1} <-- {"jsonrpc": "2.0", "result": {"URL": "/exampleurl?id=12345", "maxsize": 10000000, "accepted-format":["jpg", "png"]}, "id": 1} --> . , --> {"jsonrpc": "2.0", "method": "send_done", "params": {"checksum": "1a5e8f13"}, "id": 2} <-- {"jsonrpc": "2.0", "result": "ok"}
Error processing
The protocol itself already provides for the presence of an
error object with the
code ,
message, and
data fields. When using BaseJsonRpcServer inside the called method, you can throw an Exception in which to pass
code and
data . You can add your
message to the $ errorMessages array at a specific
code .
Exporting objects with specific fields only.
It is all up to you, how you implement it - so be it. I can only advise to create some class
ObjectToJsonConverter , in which to implement the transformation of the object into the desired array.
Example <?php class ObjectToJsonConverter { public static function GetCity( City $city ) { return array( 'id' => $city->cityId , 'name' => $city->title , 'region' => $city->region->title ); } }
Conversion to client-side objects
Here again, it all depends on you. For example, you can create the required classes and write some simple converter back (ideas on conversion can be taken in
MyShowsClient.php )
Conclusion
Hopefully, after reading the article, JSON-RPC will not be deprived of attention when choosing an interaction protocol.
Even after covering the tests> 89% of the code, I can only say: "It seems to work" :)