Compare commits

...

100 Commits

Author SHA1 Message Date
710f5f197f Make a release, create NEWS 2020-09-05 20:42:02 +02:00
ba68585d14 Streamline the manual page a bit
I have consulted `man 7 man-pages` but overall it's a huge mess.
2020-09-05 06:34:00 +02:00
984e5b4e7f Use saner defaults
So that most of the time users won't need to use any switches.

--pretty-print has been inverted into jq's --compact-output,
and --auto-id has been replaced with barely, if-at-all useful
--null-as-id.
2020-09-05 06:07:45 +02:00
d57a8bd3c7 Improve AsciiDoc compatibility
I need two renderers to work: hswg/libasciidoc and asciidoctor
in man page mode (and ideally in HTML as well).  That should be
covered now.

The triple-plus thing was the first thing that showed good results,
after trying backslashes, single-plus quoting and [] after ://.

The change of the source code block kind could be considered as
unification.  I'm combining tabs with spaces within one document
though, and I should get rid of the tabs in the rest of it then...
2020-09-05 04:36:43 +02:00
2962a644da Write a nice new man page in AsciiDoc
Taking some preliminary steps for inclusion in Linux distributions.

The help message has been slightly improved and the README extended,
with part of it now residing in the man page.

One less GNU dependency, for what it's worth.
2020-09-05 03:51:36 +02:00
6f5ef30293 Move "connecting..." messages to the debug mode
So that the --verbose option does only one thing.
2020-09-05 03:50:14 +02:00
b7b1198be7 Fix libedit a bit more and discourage from using it 2020-09-02 01:08:09 +02:00
633f7007d1 json-rpc-test-server: add a "date" method 2020-09-01 23:41:20 +02:00
f4d178b3f6 Update copyright years 2020-09-01 21:03:53 +02:00
ee5317f865 json-rpc-test-server: reject non-null params in "ping" 2020-09-01 20:56:03 +02:00
20558ecd2b json-rpc-test-server: make sure to set the id in responses 2020-09-01 20:55:22 +02:00
f6225ac6cc Warn on unexpected "id" fields 2020-09-01 20:54:23 +02:00
16ec8261dc Make the verbose mode also show the raw input
To make it more useful for debugging.  We might also tie this
to the --debug option, though that would be a bit chaotic.
2020-09-01 20:37:08 +02:00
e49ff84b74 Strip trailing newlines from editor output 2020-09-01 19:26:37 +02:00
b7c9bfd9f5 Fix libedit crash
Again, I have no idea why it started to happen, it just occured
to me to try to change the call, and it turns out out it works.
2020-09-01 19:02:41 +02:00
f6165164ee Fix prompt attributes under libedit
I can only guess why this works, and I'm not sure I want to /know/.
2020-09-01 19:02:41 +02:00
3a445c2db2 Name change 2020-09-01 19:02:41 +02:00
45d023147a Bump liberty 2020-09-01 19:02:40 +02:00
90b5364b29 Fix running helper programs
A bug was introduced in 5c38087.
2019-09-23 23:07:04 +02:00
1840ac795e json-rpc-test-server: fix some outstanding issues 2018-10-19 01:15:12 +02:00
df38bcf775 Merge in a JSON-RPC 2.0 test server 2018-10-18 07:49:36 +02:00
1c52f9e37e demo-json-rpc-server -> json-rpc-test-server 2018-10-18 07:27:55 +02:00
6e152ae37c More debugging information for static file serving 2018-10-18 07:17:06 +02:00
8d66435568 Remember to set the server context in SCGI requests 2018-10-18 06:41:12 +02:00
d883f4cc71 Finish the FastCGI backend
Bump liberty, also fixing SCGI.
2018-10-18 06:35:29 +02:00
62945cceb3 Finish the WebSocket backend
Of course, everything so far hasn't been tested much.
2018-10-18 04:44:40 +02:00
580f0a0c59 Synthesize EOF events in SCGI 2018-10-18 04:44:39 +02:00
253e35e1e4 Wrap request::write_cb in a function 2018-10-17 23:34:59 +02:00
cf56921c4e Allow WebSockets to micromanage shutdowns
They have their reasons, mostly event-related.
2018-10-17 23:26:54 +02:00
a3ec0942f8 Implement basic connection teardown
I finally understand the codebase again.  It's rather complicated.
2018-10-17 22:59:40 +02:00
efd500ca3c Accelerated daemon quitting 2018-10-17 06:08:11 +02:00
13892fcd0e Clean up client de/allocation 2018-10-17 05:16:17 +02:00
1d638c9170 Say "finalize" instead of "destroy" where appropriate
- _make() returns a struct directly
 - _init() initializes over a pointer
 - _free() deinitializes over a pointer
 - _new() is like _init() but also allocates
 - _destroy() is like _free() but also deallocates

Finalization is a matching concept in garbage-collected languages.
2018-10-17 03:53:07 +02:00
83363e6383 FastCGI: make it work at least in theory 2018-10-17 03:50:39 +02:00
272145ace2 Clarify EOF behaviour 2018-10-17 02:21:19 +02:00
4c54bc42b9 Clean up and better document client_vtable 2018-10-16 04:45:36 +02:00
7d922352ea Rename client_vtable::destroy to finalize
Matches a similar concept from garbage-collected languages.
2018-10-16 04:34:00 +02:00
7f6db9d39f Improve WebSocket shutdown 2018-10-16 04:33:59 +02:00
7cefdd496f Cleanup 2018-10-16 04:05:42 +02:00
e9530f450e Call ws_handler_start() 2018-10-16 01:47:51 +02:00
dda22c2cd5 Eliminate unnecessary user_data pointers
The CONTAINER_OF macro can find the parent structure just as well.
2018-10-16 01:47:50 +02:00
7aff9c3475 Improve documentation 2018-10-16 01:47:50 +02:00
a14edb72e9 Make Doxygen a bit more useful 2018-10-15 05:07:57 +02:00
267a9a561b Eliminate some warnings 2018-10-15 03:28:09 +02:00
441c89f654 Handle FastCGI null request IDs better 2018-10-15 03:04:39 +02:00
14ded260a0 Clarify and degrade FastCGI multiplexing
No need to support more than 255 concurrent requests on one connection.
2018-10-15 03:02:49 +02:00
8b334e9c91 Fix fcgi_muxer_send()
Outgoing records were missing padding and the reserved field.
2018-10-13 04:08:43 +02:00
fd17b4e504 Update code comments 2018-10-13 04:07:45 +02:00
d182bcef3b More transient errors 2018-10-12 20:02:42 +02:00
329fc9b88f Bump liberty
Eliminates some fall-through warnings.
2018-10-11 21:03:34 +02:00
711d73f481 Fix text message UTF-8 validation 2018-06-24 06:12:16 +02:00
df340c13ed Add the missing Date header 2018-06-24 06:12:16 +02:00
8e986a6040 Remove .travis.yml
We don't depend on any proprietary services no longer.  I'll have to
make my own replacements with blackjack and hookers.  Until then,
the file stays in the commit log as an example.
2018-06-24 06:12:16 +02:00
131debe985 Bump liberty 2018-06-24 06:12:16 +02:00
b312c022ae Update README 2018-06-24 00:42:59 +02:00
4078c8845c Relicense to 0BSD, update mail address
I've come to the conclusion that copyright mostly just stands in the way
of software development.  In my jurisdiction I cannot give up my own
copyright and 0BSD seems to be the closest thing to public domain.

The updated mail address, also used in my author/committer lines,
is shorter and looks nicer.  People rarely interact anyway.
2018-06-24 00:21:10 +02:00
333efdc70f CMakeLists.txt: fix variable name 2018-06-24 00:15:53 +02:00
5e88608286 Bump liberty and http-parser 2017-06-20 14:04:40 +02:00
ca90e9df83 Fix teardown 2017-06-20 14:01:23 +02:00
419147beec Update README 2017-06-20 14:01:10 +02:00
b85d1d74a4 Don't respond to notifications 2017-02-06 20:48:14 +01:00
d35e733c6e Bump liberty
Fixing a bug in the WebSocket frame parser.
2017-02-06 19:47:24 +01:00
e6f9e53229 Fix segfault in WebSocket parsing 2017-02-06 18:51:52 +01:00
2986f6cda0 Fix segfault on client destruction etc. 2017-02-06 18:30:02 +01:00
c4ebf2ccd5 Fix segfault on missing Sec-WebSocket-Key 2017-02-06 18:28:53 +01:00
a785dc9670 WebSocket: fix header parsing 2017-02-06 18:28:40 +01:00
2b7d455471 Fix quitting 2017-02-06 17:18:24 +01:00
0ec0685714 Bump liberty 2017-02-05 22:44:01 +01:00
733451cf2a Fix and update LICENSE 2017-02-03 22:43:09 +01:00
369f94f5ab Travis CI: brevify notifications 2017-02-03 22:38:56 +01:00
7b94a03e8c Update IRC server address 2016-03-14 21:18:27 +01:00
8b66a3f074 Bump liberty 2016-01-17 04:48:17 +01:00
f273151447 Overall revision after a year
Use something closer to inheritance for clients
2016-01-17 04:48:00 +01:00
a95867dbee Fix daemonization 2016-01-16 22:33:57 +01:00
5298d802bb Fix compiler warning 2016-01-16 22:16:01 +01:00
af3cb3aaba Bump liberty 2016-01-16 06:41:31 +01:00
0f62ef26f5 Fix FindLibMagic.cmake 2016-01-16 06:37:56 +01:00
3c7b57bba9 Convert README to AsciiDoc 2015-09-27 02:11:38 +02:00
98bbea72d2 Fix README 2015-09-27 02:10:19 +02:00
df5b7ad71a Update README 2015-05-07 20:28:24 +02:00
6785d3a9ed Implement shutdown 2015-04-10 02:44:13 +02:00
1944f9f17d Travis CI: Change IRC notification address 2015-04-10 01:46:05 +02:00
4dbdc849d9 Steady progress
On the WebSocket service.

It's not too far from being finished now.  I just have to make some
sense of the code again and make sure it's correct.

Now that json-rpc-shell should be able to run against this, I can
also finally test if both of them work as they should.
2015-04-10 01:42:41 +02:00
db6dff4216 Move a lot of stuff to liberty 2015-03-29 03:14:20 +02:00
8aa232d32e Add and fix some preliminary tests 2015-03-23 20:12:53 +01:00
6e9109df4c Don't allow whitespace in base64 2015-03-23 20:12:02 +01:00
987eae5661 Steady progress
Renamed some constants, added basic UTF-8 validation.
2015-03-23 16:47:21 +01:00
9b7dd630e3 WebSockets improvements
- validate more HTTP stuff, use the newer RFC
 - validate the base64 key
2015-03-22 22:35:58 +01:00
c87d684154 Steady progress
Started parsing Content-Type properly after studying the HTTP RFC
for a significant period of time.

Some further WebSockets stuff.
2015-03-15 04:32:04 +01:00
23eb4cca38 Steady progress
Still in a state of total chaos, it appears.
2015-03-14 19:37:00 +01:00
4337038819 Try to lock a PID file 2015-03-11 23:57:25 +01:00
012a57b357 Steady progress
Some further refactoring, added a few comments, etc.

It's not about adding huge chunks of code anymore, and I'm slowly
moving towards getting the details right.

There's still a ton of TODO items, though.
2015-03-11 00:25:46 +01:00
3c0e48a429 Refactoring 2015-03-10 20:48:25 +01:00
5885d1aa69 Some intial WebSockets code 2015-03-09 23:32:01 +01:00
931fc441f6 Steady progress
Added static content serving with sane content type detection.

Started working on WebSockets (meanwhile neither SCGI or FastCGI is
finished and almost nothing has been tested).
2015-03-08 09:41:10 +01:00
9e0c9dd6d8 Steady progress
Still trying to figure out FastCGI.

At least I've finally implemented the JSON-RPC handler.
2015-03-08 05:51:51 +01:00
2733ead30f Figuring out how to close the connection 2015-03-06 19:49:33 +01:00
0b0d64124b Steady progress 2015-03-05 08:47:20 +01:00
a54230bddb Steady progress
I'm trying to figure out everything at once, i.e. the entire structure
of the application from top to bottom, trying to converge on a workable
design while refactoring still doesn't hurt as much as it would once
it's established.
2015-03-02 23:11:29 +01:00
8a3241d5c4 Initial commit
Not even the demo is able to compile yet.

I'm just tracking my progress.
2015-03-02 19:14:37 +01:00
9 changed files with 3309 additions and 74 deletions

View File

@@ -13,8 +13,8 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# Version
set (project_VERSION_MAJOR "0")
set (project_VERSION_MINOR "1")
set (project_VERSION_MAJOR "1")
set (project_VERSION_MINOR "0")
set (project_VERSION_PATCH "0")
set (project_VERSION "${project_VERSION_MAJOR}")
@@ -70,6 +70,16 @@ include_directories (${PROJECT_BINARY_DIR})
add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c http-parser/http_parser.c)
target_link_libraries (${PROJECT_NAME} ${project_libraries})
# Development tools
find_package (LibMagic)
if (LIBMAGIC_FOUND)
include_directories (${LIBMAGIC_INCLUDE_DIRS})
add_executable (json-rpc-test-server
json-rpc-test-server.c http-parser/http_parser.c)
target_link_libraries (json-rpc-test-server
${project_libraries} ${LIBMAGIC_LIBRARIES})
endif ()
# The files to be installed
include (GNUInstallDirs)
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
@@ -77,18 +87,20 @@ install (PROGRAMS json-format.pl DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# Generate documentation from program help
find_program (HELP2MAN_EXECUTABLE help2man)
if (NOT HELP2MAN_EXECUTABLE)
message (FATAL_ERROR "help2man not found")
endif (NOT HELP2MAN_EXECUTABLE)
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
if (NOT ASCIIDOCTOR_EXECUTABLE)
message (FATAL_ERROR "asciidoctor not found")
endif (NOT ASCIIDOCTOR_EXECUTABLE)
foreach (page ${PROJECT_NAME})
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
list (APPEND project_MAN_PAGES "${page_output}")
add_custom_command (OUTPUT ${page_output}
COMMAND ${HELP2MAN_EXECUTABLE} -N
"${PROJECT_BINARY_DIR}/${page}" -o ${page_output}
DEPENDS ${page}
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
-a release-version=${project_VERSION}
"${PROJECT_SOURCE_DIR}/${page}.adoc"
-o "${page_output}"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
endforeach (page)
@@ -101,9 +113,10 @@ foreach (page ${project_MAN_PAGES})
endforeach (page)
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Shell for running JSON-RPC 2.0 queries")
set (CPACK_PACKAGE_VENDOR "Premysl Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p@janouch.name>")
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY
"A shell for running JSON-RPC 2.0 queries")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR})
set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR})

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014 - 2016, Přemysl Janouch <p@janouch.name>
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

4
NEWS Normal file
View File

@@ -0,0 +1,4 @@
1.0.0 (2020-09-05)
* Initial release

View File

@@ -17,36 +17,29 @@ you get the following niceties:
- ability to pipe output through a shell command, so that you can view the
results in your favourite editor or redirect them to a file
- ability to edit the input line in your favourite editor as well with Alt+E
- WebSockets (RFC 6455) can also be used as a transport rather than HTTP
Supported transports
--------------------
- HTTP
- HTTPS
- WebSocket
- WebSocket over TLS
WebSockets
~~~~~~~~~~
The JSON-RPC 2.0 spec doesn't say almost anything about underlying transports.
The way it's implemented here is that every request is sent as a single text
message. If it has an "id" field, i.e. it's not just a notification, the
client waits for a message from the server in response.
There's no support so far for any protocol extensions, nor for specifying
the higher-level protocol (the "Sec-Ws-Protocol" HTTP field).
Documentation
-------------
See the link:json-rpc-shell.adoc[man page] for information about usage.
The rest of this README will concern itself with externalities.
Packages
--------
Regular releases are sporadic. git master should be stable enough. You can get
a package with the latest development version from Archlinux's AUR.
Building and Usage
------------------
Build dependencies: CMake, pkg-config, help2man,
Building
--------
Build dependencies: CMake, pkg-config, asciidoctor,
liberty (included), http-parser (included) +
Runtime dependencies: libev, Jansson, cURL, openssl,
readline or libedit >= 2013-07-12,
Avoid libedit if you can, in general it works but at the moment history is
acting up and I have no clue about fixing it. Multiline editing is also
misbehaving there.
$ git clone --recursive https://git.janouch.name/p/json-rpc-shell.git
$ mkdir json-rpc-shell/build
$ cd json-rpc-shell/build
@@ -65,7 +58,12 @@ Or you can try telling CMake to make a package for you. For Debian it is:
Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
`fakeroot` or file ownership will end up wrong.
Run the program with `--help` to obtain usage information.
Test server
-----------
If you install development packages for libmagic, an included test server will
be built but not installed which provides a trivial JSON-RPC 2.0 service with
FastCGI, SCGI, and WebSocket interfaces. It responds to `ping` and `date`
methods and it can serve static files.
Contributing and Support
------------------------

10
cmake/FindLibMagic.cmake Normal file
View File

@@ -0,0 +1,10 @@
# Public Domain
find_library (LIBMAGIC_LIBRARIES magic)
find_path (LIBMAGIC_INCLUDE_DIRS magic.h)
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (LibMagic DEFAULT_MSG
LIBMAGIC_LIBRARIES LIBMAGIC_INCLUDE_DIRS)
mark_as_advanced (LIBMAGIC_LIBRARIES LIBMAGIC_INCLUDE_DIRS)

180
json-rpc-shell.adoc Normal file
View File

@@ -0,0 +1,180 @@
json-rpc-shell(1)
=================
:doctype: manpage
:manmanual: json-rpc-shell Manual
:mansource: json-rpc-shell {release-version}
Name
----
json-rpc-shell - a simple JSON-RPC 2.0 shell
Synopsis
--------
*json-rpc-shell* [_OPTION_]... _ENDPOINT_
Description
-----------
The _ENDPOINT_ must be either an HTTP or a WebSocket URL, with or without TLS
(i.e. one of the _+++http+++://_, _+++https+++://_, _ws://_, _wss://_ schemas).
*json-rpc-shell* will use it to send any JSON-RPC 2.0 requests you enter on its
command line. The server's response will be parsed and validated, stripping it
of the protocol's noisy envelope. At your option, it can then also be
pretty-printed, rendered with adjustable syntax highlighting, or even piped
through another program such as the *less*(1) pager or the *jq*(1) JSON
processor.
Usage
~~~~~
Three things may appear on the internal command line, in a sequence. The first
one is always the name of the JSON-RPC method to call, as a bare word, separated
from the rest by white space. Following that, you may enter three kinds of JSON
values. If it is an object or an array, it constitutes the method parameters.
If it is a string or a number, it is taken as the "id" to use for the request,
which would be chosen for you automatically if left unspecified. Finally,
a null value indicates that the request should be sent as a notification,
lacking the ID completely. Booleans cannot be used for anything.
The response to the method call may be piped through external commands, the same
way you would do it in a Unix shell.
Exit the program by pressing C-c or C-d. No special keywords are reserved for
this action as they might conflict with method names.
Options
-------
Controlling output
~~~~~~~~~~~~~~~~~~
*-c*, *--compact-output*::
Do not pretty-print responses. Normally, spaces and newlines are added
where appropriate to improve readability.
*--color*=_WHEN_::
By default, when the output of the program is a terminal, JSON responses
are syntax-highlighted. This corresponds to the _auto_ setting. You may
also set this to _always_ or _never_. In either case, color is never
applied when piping to another program.
*-v*, *--verbose*::
Print raw requests and responses, including the JSON-RPC 2.0 envelope.
*-d*, *--debug*::
Print even more information to help debug various issues.
Protocol
~~~~~~~~
*-n*, *--null-as-id*::
Normally, entering a null JSON value on the command line causes
a notification to be sent. With this option, it is sent as the "id"
field of a normal request, which is discouraged by the specification.
*-t*, *--trust-all*::
Trust all SSL/TLS certificates. Useful in case that the certificate is
self-signed, or when the CA isn't in your CA store. Beware that this option
is about as good as using plain unencrypted HTTP.
*-o* _ORIGIN_, *--origin*=_ORIGIN_::
Set the HTTP Origin header to _ORIGIN_. Some servers may need this.
Program information
~~~~~~~~~~~~~~~~~~~
*-h*, *--help*::
Display a help message and exit.
*-V*, *--version*::
Output version information and exit.
*--write-default-cfg*[**=**__PATH__]::
Write a default configuration file, show its path and exit.
Files
-----
_~/.config/json-rpc-shell/json-rpc-shell.conf_::
The configuration file, in which you can configure color output and
CA certificate paths. Use the *--write-default-cfg* option to create
a new one for editing.
_~/.local/share/json-rpc-shell/history_::
All your past method invocations are stored here upon exit and loaded back
on start-up.
Notes
-----
Editing
~~~~~~~
While single-line editing on the command line may be satisfactory for simple
requests, it is often convenient or even necessary to run a full text editor
in order to construct complex objects or arrays, and may even be used to import
data from elsewhere. You can launch an editor for the current request using
the M-e key combination. Both *readline*(3) and *editline*(7) also support
multiline editing natively, though you need to press C-v C-j in order to insert
newlines.
WebSockets
~~~~~~~~~~
The JSON-RPC 2.0 specification doesn't say almost anything about underlying
transports. As far as the author is aware, he is the only person combining it
with WebSockets. The way it's implemented here is that every request is sent as
a single text message. If it has an "id" field, i.e., it's not just
a notification, the client waits for a message from the server in response.
Should any message arrive unexpectedly, you will receive a warning.
There is no support so far for any protocol extensions, nor for specifying
the higher-level protocol (the "Sec-Ws-Protocol" HTTP field).
Bugs
----
The editline (libedit) frontend is more of a proof of concept that mostly seems
to work but exhibits bugs that are not our fault.
Examples
--------
Running some queries against json-rpc-test-server, included in the source
distribution of this program (public services are hard to find):
Methods without parameters
~~~~~~~~~~~~~~~~~~~~~~~~~~
$ json-rpc-shell ws://localhost:1234
json-rpc> ping
"pong"
json-rpc> date
{
"year": 2020,
"month": 9,
"day": 5,
"hours": 2,
"minutes": 23,
"seconds": 51
}
Notification with a parameter
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Notifications never produce a response, not even when the method is not known
to the server:
$ json-rpc-shell ws://localhost:1234
json-rpc> notify {"events": ["conquest", "war", "famine", "death"]} null
[Notification]
Piping in and out
~~~~~~~~~~~~~~~~~
GNU Readline always repeats the prompt, which makes this a bit less useful
for invoking from other programs:
$ echo 'ping | jq ascii_upcase' | json-rpc-shell ws://localhost:1234
json-rpc> ping | jq ascii_upcase
"PONG"
Reporting bugs
--------------
Use https://git.janouch.name/p/json-rpc-shell to report bugs, request features,
or submit pull requests.
See also
--------
*jq*(1), *readline*(3) or *editline*(7)
Specifications
~~~~~~~~~~~~~~
https://www.jsonrpc.org/specification +
https://www.json.org

View File

@@ -1,7 +1,7 @@
/*
* json-rpc-shell.c: simple JSON-RPC 2.0 shell
*
* Copyright (c) 2014 - 2016, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -531,7 +531,8 @@ input_el_redisplay (struct input_el *self)
el_push (self->editline, x);
// We have to do this or it gets stuck and nothing is done
(void) el_gets (self->editline, NULL);
int count = 0;
(void) el_wgets (self->editline, &count);
}
static char *
@@ -618,7 +619,6 @@ input_el_on_run_editor (EditLine *editline, int key)
static void
input_el_install_prompt (struct input_el *self)
{
// XXX: the ignore doesn't quite work, see https://gnats.netbsd.org/47539
el_set (self->editline, EL_PROMPT_ESC,
input_el_make_prompt, INPUT_START_IGNORE);
}
@@ -633,7 +633,7 @@ input_el_start (struct input *input, const char *program_name)
el_set (self->editline, EL_CLIENTDATA, self);
input_el_install_prompt (self);
el_set (self->editline, EL_SIGNAL, false);
el_set (self->editline, EL_UNBUFFERED, true);
el_set (self->editline, EL_UNBUFFERED, isatty (fileno (stdin)));
el_set (self->editline, EL_EDITOR, "emacs");
el_wset (self->editline, EL_HIST, history_w, self->history);
@@ -833,6 +833,17 @@ input_el_on_tty_readable (struct input *input)
int count = 0;
const wchar_t *buf = el_wgets (self->editline, &count);
// Editline works in a funny NO_TTY mode when the input is not a tty,
// we cannot use EL_UNBUFFERED and expect sane results then
int unbuffered = 0;
if (!el_get (self->editline, EL_UNBUFFERED, &unbuffered) && !unbuffered)
{
char *entered_line = buf ? input_el_wcstombs (buf) : NULL;
self->super.on_input (entered_line, self->super.user_data);
free (entered_line);
return;
}
// Process data from our newline handler (async-friendly handling)
if (self->entered_line)
{
@@ -921,11 +932,11 @@ static struct app_context
struct config config; ///< Program configuration
enum color_mode color_mode; ///< Colour output mode
bool pretty_print; ///< Whether to pretty print
bool compact; ///< Whether to not pretty print
bool verbose; ///< Print requests
bool trust_all; ///< Don't verify peer certificates
bool auto_id; ///< Use automatically generated ID's
bool null_as_id; ///< JSON null is used as an ID
int64_t next_id; ///< Next autogenerated ID
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
@@ -1672,7 +1683,7 @@ backend_ws_establish_connection (struct ws_context *self,
else
real_host = buf;
if (self->ctx->verbose)
if (g_debug_mode)
{
char *address = format_host_port_pair (real_host, port);
print_status ("connecting to %s...", address);
@@ -2738,8 +2749,19 @@ display_via_pipeline (struct app_context *ctx,
}
static bool
parse_response (struct app_context *ctx, struct str *buf, const char *pipeline)
process_response (struct app_context *ctx, const json_t *id, struct str *buf,
const char *pipeline)
{
if (!id)
{
printf ("[Notification]\n");
if (!buf->len)
return true;
print_warning ("we have been sent data back for a notification");
return false;
}
json_error_t e;
json_t *response;
if (!(response = json_loadb (buf->str, buf->len, JSON_DECODE_ANY, &e)))
@@ -2758,9 +2780,15 @@ parse_response (struct app_context *ctx, struct str *buf, const char *pipeline)
else if (!json_is_string (v) || strcmp (json_string_value (v), "2.0"))
print_warning ("invalid `%s' field in response", "jsonrpc");
json_t *result = json_object_get (response, "result");
json_t *error = json_object_get (response, "error");
json_t *data = NULL;
json_t *returned_id = json_object_get (response, "id");
json_t *result = json_object_get (response, "result");
json_t *error = json_object_get (response, "error");
json_t *data = NULL;
if (!returned_id)
print_warning ("`%s' field not present in response", "id");
if (!json_equal (id, returned_id))
print_warning ("mismatching `%s' field in response", "id");
if (!result && !error)
PARSE_FAIL ("neither `result' nor `error' present in response");
@@ -2817,7 +2845,7 @@ parse_response (struct app_context *ctx, struct str *buf, const char *pipeline)
if (result)
{
int flags = JSON_ENCODE_ANY;
if (ctx->pretty_print)
if (!ctx->compact)
flags |= JSON_INDENT (2);
char *utf8 = json_dumps (result, flags);
@@ -2891,26 +2919,28 @@ make_json_rpc_call (struct app_context *ctx,
goto fail;
}
bool success = false;
if (id)
success = parse_response (ctx, &buf, pipeline);
else
if (ctx->verbose)
{
printf ("[Notification]\n");
if (buf.len)
print_warning ("we have been sent data back for a notification");
char *buf_term =
iconv_xstrdup (ctx->term_from_utf8, buf.str, buf.len, NULL);
if (!buf_term)
print_error ("%s: %s", "verbose", "character conversion failed");
else
success = true;
{
print_attributed (ctx, stdout, ATTR_INCOMING, "%s", buf_term);
fputs ("\n", stdout);
}
free (buf_term);
}
if (!success)
if (!process_response (ctx, id, &buf, pipeline))
{
char *s = iconv_xstrdup (ctx->term_from_utf8,
buf.str, buf.len + 1 /* null byte */, NULL);
if (!s)
print_error ("character conversion failed for `%s'",
"raw response data");
else
else if (!ctx->verbose /* already printed */)
printf ("%s: %s\n", "raw response data", s);
free (s);
}
@@ -3011,9 +3041,16 @@ process_input (char *user_input, void *user_data)
*target = json_incref (args[i]);
}
if (!id && ctx->auto_id)
if (!id)
id = json_integer (ctx->next_id++);
// Use nulls to send notifications, unless a special switch is used
if (!ctx->null_as_id && json_is_null (id))
{
json_decref (id);
id = NULL;
}
make_json_rpc_call (ctx, method, id, params, pipeline);
fail_parse:
@@ -3176,16 +3213,21 @@ process_edited_input (struct app_context *ctx)
print_error ("%s: %s", "input editing failed", e->message);
error_free (e);
}
else if (!ctx->input->vtable->replace_line (ctx->input, input.str))
print_error ("%s: %s", "input editing failed",
"could not re-insert modified text");
else
{
// Strip trailing newlines, added automatically by editors
while (input.len && strchr ("\r\n", input.str[input.len - 1]))
input.str[--input.len] = 0;
if (!ctx->input->vtable->replace_line (ctx->input, input.str))
print_error ("%s: %s", "input editing failed",
"could not re-insert modified text");
}
if (unlink (ctx->editor_filename))
print_error ("could not unlink `%s': %s",
ctx->editor_filename, strerror (errno));
free (ctx->editor_filename);
ctx->editor_filename = NULL;
str_free (&input);
}
@@ -3203,7 +3245,7 @@ on_child (EV_P_ ev_child *handle, int revents)
kill (-handle->rpid, SIGKILL);
return;
}
// I don't recognize this child (we should also check PID)
// I don't recognize this child (we should also check its PID)
if (!ctx->editor_filename)
return;
@@ -3216,6 +3258,9 @@ on_child (EV_P_ ev_child *handle, int revents)
print_error ("editor returned status %d", WEXITSTATUS (status));
else
process_edited_input (ctx);
free (ctx->editor_filename);
ctx->editor_filename = NULL;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -3250,8 +3295,14 @@ on_tty_readable (EV_P_ ev_io *handle, int revents)
{
// rl_callback_read_char() is not reentrant, may happen on EOF
ev_io_stop (EV_DEFAULT_ &ctx->tty_watcher);
ctx->input->vtable->on_tty_readable (ctx->input);
ev_io_start (EV_DEFAULT_ &ctx->tty_watcher);
// Don't make ourselves receive a SIGTTIN. Ideally we'd prevent
// reentrancy without inciting conflicts with
// {suspend,resume}_terminal() but I can't figure anything out.
if (!ctx->editor_filename)
ev_io_start (EV_DEFAULT_ &ctx->tty_watcher);
}
}
@@ -3295,14 +3346,14 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
static const struct opt opts[] =
{
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'h', "help", NULL, 0, "display this help message and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
{ 'a', "auto-id", NULL, 0, "automatic `id' fields" },
{ 'n', "null-as-id", NULL, 0, "JSON null is used as an `id'" },
{ 'o', "origin", "O", 0, "set the HTTP Origin header" },
{ 'p', "pretty", NULL, 0, "pretty-print the responses" },
{ 'c', "compact-output", NULL, 0, "do not pretty-print responses" },
{ 't', "trust-all", NULL, 0, "don't care about SSL/TLS certificates" },
{ 'v', "verbose", NULL, 0, "print the request before sending" },
{ 'c', "color", "WHEN", OPT_LONG_ONLY,
{ 'v', "verbose", NULL, 0, "print raw requests and responses" },
{ 'C', "color", "WHEN", OPT_LONG_ONLY,
"colorize output: never, always, or auto" },
{ 'w', "write-default-cfg", "FILENAME",
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
@@ -3311,7 +3362,7 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
};
struct opt_handler oh = opt_handler_make (argc, argv, opts,
"ENDPOINT", "Simple JSON-RPC shell.");
"ENDPOINT", "A simple JSON-RPC 2.0 shell.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
@@ -3328,12 +3379,12 @@ parse_program_arguments (struct app_context *ctx, int argc, char **argv,
exit (EXIT_SUCCESS);
case 'o': *origin = optarg; break;
case 'a': ctx->auto_id = true; break;
case 'p': ctx->pretty_print = true; break;
case 'n': ctx->null_as_id = true; break;
case 'c': ctx->compact = true; break;
case 't': ctx->trust_all = true; break;
case 'v': ctx->verbose = true; break;
case 'c':
case 'C':
if (!strcasecmp (optarg, "never"))
ctx->color_mode = COLOR_NEVER;
else if (!strcasecmp (optarg, "always"))
@@ -3453,7 +3504,7 @@ main (int argc, char *argv[])
{
// XXX: to be completely correct, we should use tputs, but we cannot
g_ctx.input->vtable->set_prompt (g_ctx.input,
xstrdup_printf ("%c%s%cjson-rpc> %c%s%c",
xstrdup_printf ("%c%s%cjson-rpc>%c%s%c ",
INPUT_START_IGNORE, g_ctx.attrs[ATTR_PROMPT],
INPUT_END_IGNORE,
INPUT_START_IGNORE, g_ctx.attrs[ATTR_RESET],

2979
json-rpc-test-server.c Normal file

File diff suppressed because it is too large Load Diff

Submodule liberty updated: bb30c7d86e...1a76b2032e