Compare commits
75 Commits
b7b1198be7
...
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
74fcb06828
|
|||
|
8cf1abf135
|
|||
|
b87fbc93a6
|
|||
|
ac1a21eac8
|
|||
|
6934550068
|
|||
|
cda7e1b1f3
|
|||
|
14c6d285fc
|
|||
|
ab5941aaef
|
|||
|
84d6c658e8
|
|||
|
a89fadf860
|
|||
|
4023155b67
|
|||
|
4ed58dd89a
|
|||
|
022668fb23
|
|||
|
ba5a6374b6
|
|||
|
67008963cf
|
|||
|
c1b6918db3
|
|||
|
3cf3c0215e
|
|||
|
a2a72c8b92
|
|||
|
57f89eba07
|
|||
|
4795ee851d
|
|||
|
87a644cc59
|
|||
|
990cf5a1d4
|
|||
|
4a5c818ba1
|
|||
|
af5929a383
|
|||
|
9f5845fc51
|
|||
|
3daf254b41
|
|||
|
c533fa2fd7
|
|||
|
2fe2d6bc03
|
|||
|
df93937789
|
|||
|
ae447065f7
|
|||
|
f9e157293c
|
|||
|
42d1ff064f
|
|||
|
710f8e0b2d
|
|||
|
4938ee43bd
|
|||
|
6927d022fb
|
|||
|
75b2094cdd
|
|||
|
b3c377afdb
|
|||
|
4236a4943a
|
|||
|
23c728e535
|
|||
|
dfe814316f
|
|||
|
efc663a178
|
|||
|
2b8f52ac72
|
|||
|
bb7ffe1da2
|
|||
|
ad1aba9d22
|
|||
|
0107d09abc
|
|||
|
01767198f2
|
|||
|
5854ed1b32
|
|||
|
63c8a79479
|
|||
|
d489362a28
|
|||
|
c87869bef7
|
|||
|
fcf65f8377
|
|||
|
d820bc2f23
|
|||
|
b458fc1f99
|
|||
|
0771c142fe
|
|||
|
742632a931
|
|||
|
2221828763
|
|||
|
c2a00511c0
|
|||
|
2b18ebf314
|
|||
|
5d2cd01db0
|
|||
|
ee79249d23
|
|||
|
160d23018a
|
|||
|
fed2892ee1
|
|||
|
667b01cb73
|
|||
|
20c8578084
|
|||
|
57a3b4e990
|
|||
|
e4d1529b4d
|
|||
|
897a263ee7
|
|||
|
84702fa47d
|
|||
|
b315892249
|
|||
|
710f5f197f
|
|||
|
ba68585d14
|
|||
|
984e5b4e7f
|
|||
|
d57a8bd3c7
|
|||
|
2962a644da
|
|||
|
6f5ef30293
|
33
.clang-format
Normal file
33
.clang-format
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# clang-format is fairly limited, and these rules are approximate:
|
||||||
|
# - array initializers can get terribly mangled with clang-format 12.0,
|
||||||
|
# - sometimes it still aligns with space characters,
|
||||||
|
# - EV_DEFAULT_ and EV_A_ are always taken as identifiers,
|
||||||
|
# - struct name NL { NL ... NL } NL name; is unachievable.
|
||||||
|
BasedOnStyle: GNU
|
||||||
|
ColumnLimit: 80
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: ForContinuationAndIndentation
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
AlignAfterOpenBracket: DontAlign
|
||||||
|
AlignOperands: DontAlign
|
||||||
|
AlignConsecutiveMacros: Consecutive
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
IndentGotoLabels: false
|
||||||
|
|
||||||
|
# IncludeCategories has some potential, but it may also break the build.
|
||||||
|
# Note that the documentation says the value should be "Never".
|
||||||
|
SortIncludes: false
|
||||||
|
|
||||||
|
# This is a compromise, it generally works out aesthetically better.
|
||||||
|
BinPackArguments: false
|
||||||
|
|
||||||
|
# Unfortunately, this can't be told to align to column 40 or so.
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
|
|
||||||
|
# liberty-specific macro body wrappers.
|
||||||
|
MacroBlockBegin: "BLOCK_START"
|
||||||
|
MacroBlockEnd: "BLOCK_END"
|
||||||
|
ForEachMacros: ["LIST_FOR_EACH"]
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,3 +7,5 @@
|
|||||||
/json-rpc-shell.files
|
/json-rpc-shell.files
|
||||||
/json-rpc-shell.creator*
|
/json-rpc-shell.creator*
|
||||||
/json-rpc-shell.includes
|
/json-rpc-shell.includes
|
||||||
|
/json-rpc-shell.cflags
|
||||||
|
/json-rpc-shell.cxxflags
|
||||||
|
|||||||
130
CMakeLists.txt
130
CMakeLists.txt
@@ -1,5 +1,5 @@
|
|||||||
project (json-rpc-shell C)
|
cmake_minimum_required (VERSION 3.0...3.27)
|
||||||
cmake_minimum_required (VERSION 2.8.5)
|
project (json-rpc-shell VERSION 1.2.0 LANGUAGES C)
|
||||||
|
|
||||||
# Options
|
# Options
|
||||||
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
|
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
|
||||||
@@ -10,54 +10,73 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
|||||||
# -Wunused-function is pretty annoying here, as everything is static
|
# -Wunused-function is pretty annoying here, as everything is static
|
||||||
set (CMAKE_C_FLAGS
|
set (CMAKE_C_FLAGS
|
||||||
"${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Wno-unused-function")
|
"${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Wno-unused-function")
|
||||||
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
endif ()
|
||||||
|
|
||||||
# Version
|
|
||||||
set (project_VERSION_MAJOR "0")
|
|
||||||
set (project_VERSION_MINOR "1")
|
|
||||||
set (project_VERSION_PATCH "0")
|
|
||||||
|
|
||||||
set (project_VERSION "${project_VERSION_MAJOR}")
|
|
||||||
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
|
|
||||||
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
|
||||||
|
|
||||||
# For custom modules
|
# For custom modules
|
||||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
set (CMAKE_MODULE_PATH
|
||||||
|
"${PROJECT_SOURCE_DIR}/cmake;${PROJECT_SOURCE_DIR}/liberty/cmake")
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package (Curses)
|
find_package (Curses)
|
||||||
|
find_package (Ncursesw)
|
||||||
find_package (PkgConfig REQUIRED)
|
find_package (PkgConfig REQUIRED)
|
||||||
pkg_check_modules (dependencies REQUIRED libcurl jansson)
|
|
||||||
# Note that cURL can link to a different version of libssl than we do,
|
# Note that cURL can link to a different version of libssl than we do,
|
||||||
# in which case the results are undefined
|
# in which case the results are undefined
|
||||||
pkg_check_modules (libssl REQUIRED libssl libcrypto)
|
pkg_check_modules (dependencies REQUIRED libcurl jansson libssl libcrypto)
|
||||||
find_package (LibEV REQUIRED)
|
find_package (LibEV REQUIRED)
|
||||||
pkg_check_modules (ncursesw ncursesw)
|
|
||||||
|
|
||||||
set (project_libraries ${dependencies_LIBRARIES}
|
set (project_libraries ${dependencies_LIBRARIES} ${LibEV_LIBRARIES})
|
||||||
${libssl_LIBRARIES} ${LIBEV_LIBRARIES})
|
include_directories (${dependencies_INCLUDE_DIRS} ${LibEV_INCLUDE_DIRS})
|
||||||
include_directories (${dependencies_INCLUDE_DIRS}
|
link_directories (${dependencies_LIBRARY_DIRS})
|
||||||
${libssl_INCLUDE_DIRS} ${LIBEV_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
if (ncursesw_FOUND)
|
if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
|
||||||
list (APPEND project_libraries ${ncursesw_LIBRARIES})
|
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
|
||||||
include_directories (${ncursesw_INCLUDE_DIRS})
|
# our POSIX version macros make it undefined
|
||||||
|
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
|
||||||
|
elseif (APPLE)
|
||||||
|
add_definitions (-D_DARWIN_C_SOURCE)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# -liconv may or may not be a part of libc
|
||||||
|
find_library (iconv_LIBRARIES iconv)
|
||||||
|
if (iconv_LIBRARIES)
|
||||||
|
list (APPEND project_libraries ${iconv_LIBRARIES})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include (CheckCSourceRuns)
|
||||||
|
set (CMAKE_REQUIRED_LIBRARIES ${project_libraries})
|
||||||
|
get_property (CMAKE_REQUIRED_INCLUDES
|
||||||
|
DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY INCLUDE_DIRECTORIES)
|
||||||
|
CHECK_C_SOURCE_RUNS ("#include <iconv.h>
|
||||||
|
int main () { return iconv_open (\"UTF-8//TRANSLIT\", \"ISO-8859-1\")
|
||||||
|
== (iconv_t) -1; }" ICONV_ACCEPTS_TRANSLIT)
|
||||||
|
|
||||||
|
if (Ncursesw_FOUND)
|
||||||
|
list (APPEND project_libraries ${Ncursesw_LIBRARIES})
|
||||||
|
include_directories (${Ncursesw_INCLUDE_DIRS})
|
||||||
|
link_directories (${Ncursesw_LIBRARY_DIRS})
|
||||||
elseif (CURSES_FOUND)
|
elseif (CURSES_FOUND)
|
||||||
list (APPEND project_libraries ${CURSES_LIBRARY})
|
list (APPEND project_libraries ${CURSES_LIBRARY})
|
||||||
include_directories (${CURSES_INCLUDE_DIR})
|
include_directories (${CURSES_INCLUDE_DIR})
|
||||||
else (CURSES_FOUND)
|
else ()
|
||||||
message (SEND_ERROR "Curses not found")
|
message (SEND_ERROR "Curses not found")
|
||||||
endif (ncursesw_FOUND)
|
endif ()
|
||||||
|
|
||||||
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
|
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
|
||||||
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
|
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
|
||||||
elseif (WANT_READLINE)
|
elseif (WANT_READLINE)
|
||||||
list (APPEND project_libraries readline)
|
# OpenBSD's default readline is too old
|
||||||
|
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
|
||||||
|
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
|
||||||
|
list (APPEND project_libraries ereadline)
|
||||||
|
else ()
|
||||||
|
list (APPEND project_libraries readline)
|
||||||
|
endif ()
|
||||||
elseif (WANT_LIBEDIT)
|
elseif (WANT_LIBEDIT)
|
||||||
pkg_check_modules (libedit REQUIRED libedit)
|
pkg_check_modules (libedit REQUIRED libedit)
|
||||||
list (APPEND project_libraries ${libedit_LIBRARIES})
|
list (APPEND project_libraries ${libedit_LIBRARIES})
|
||||||
include_directories (${libedit_INCLUDE_DIRS})
|
include_directories (${libedit_INCLUDE_DIRS})
|
||||||
endif ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
|
endif ()
|
||||||
|
|
||||||
# Generate a configuration file
|
# Generate a configuration file
|
||||||
set (HAVE_READLINE "${WANT_READLINE}")
|
set (HAVE_READLINE "${WANT_READLINE}")
|
||||||
@@ -86,21 +105,43 @@ install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
|||||||
install (PROGRAMS json-format.pl DESTINATION ${CMAKE_INSTALL_BINDIR})
|
install (PROGRAMS json-format.pl DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
||||||
# Generate documentation from program help
|
# Generate documentation from text markup
|
||||||
find_program (HELP2MAN_EXECUTABLE help2man)
|
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
|
||||||
if (NOT HELP2MAN_EXECUTABLE)
|
find_program (A2X_EXECUTABLE a2x)
|
||||||
message (FATAL_ERROR "help2man not found")
|
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
|
||||||
endif (NOT HELP2MAN_EXECUTABLE)
|
message (WARNING "Neither asciidoctor nor a2x were found, "
|
||||||
|
"falling back to a substandard manual page generator")
|
||||||
|
endif ()
|
||||||
|
|
||||||
foreach (page ${PROJECT_NAME})
|
foreach (page ${PROJECT_NAME})
|
||||||
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
|
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
|
||||||
list (APPEND project_MAN_PAGES "${page_output}")
|
list (APPEND project_MAN_PAGES "${page_output}")
|
||||||
add_custom_command (OUTPUT ${page_output}
|
if (ASCIIDOCTOR_EXECUTABLE)
|
||||||
COMMAND ${HELP2MAN_EXECUTABLE} -N
|
add_custom_command (OUTPUT ${page_output}
|
||||||
"${PROJECT_BINARY_DIR}/${page}" -o ${page_output}
|
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
|
||||||
DEPENDS ${page}
|
-a release-version=${PROJECT_VERSION}
|
||||||
COMMENT "Generating man page for ${page}" VERBATIM)
|
-o "${page_output}"
|
||||||
endforeach (page)
|
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||||
|
DEPENDS ${page}.adoc
|
||||||
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
|
elseif (A2X_EXECUTABLE)
|
||||||
|
add_custom_command (OUTPUT ${page_output}
|
||||||
|
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
|
||||||
|
-a release-version=${PROJECT_VERSION}
|
||||||
|
-D "${PROJECT_BINARY_DIR}"
|
||||||
|
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||||
|
DEPENDS ${page}.adoc
|
||||||
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
|
else ()
|
||||||
|
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
|
||||||
|
add_custom_command (OUTPUT ${page_output}
|
||||||
|
COMMAND env LC_ALL=C asciidoc-release-version=${PROJECT_VERSION}
|
||||||
|
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||||
|
> ${page_output}
|
||||||
|
DEPENDS ${page}.adoc ${ASCIIMAN}
|
||||||
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
|
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
|
||||||
|
|
||||||
@@ -108,22 +149,19 @@ foreach (page ${project_MAN_PAGES})
|
|||||||
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
||||||
install (FILES "${page}"
|
install (FILES "${page}"
|
||||||
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
|
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
|
||||||
endforeach (page)
|
endforeach ()
|
||||||
|
|
||||||
# CPack
|
# CPack
|
||||||
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Shell for running JSON-RPC 2.0 queries")
|
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "A shell for JSON-RPC 2.0")
|
||||||
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
||||||
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
||||||
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
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})
|
|
||||||
set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH})
|
|
||||||
set (CPACK_GENERATOR "TGZ;ZIP")
|
set (CPACK_GENERATOR "TGZ;ZIP")
|
||||||
set (CPACK_PACKAGE_FILE_NAME
|
set (CPACK_PACKAGE_FILE_NAME
|
||||||
"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}")
|
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${PROJECT_VERSION}")
|
||||||
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
||||||
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
|
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
|
||||||
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}")
|
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
|
||||||
|
|
||||||
include (CPack)
|
include (CPack)
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
|||||||
34
NEWS
Normal file
34
NEWS
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
1.2.0 (2024-12-24)
|
||||||
|
|
||||||
|
* Add a backend for co-processes, such as language servers
|
||||||
|
|
||||||
|
* Support reading OpenRPC documents from a file
|
||||||
|
|
||||||
|
* Respect the NO_COLOR environment variable
|
||||||
|
|
||||||
|
* Miscellaneous libedit (editline) fixes
|
||||||
|
|
||||||
|
* Miscellaneous portability improvements
|
||||||
|
|
||||||
|
* json-rpc-test-server: implement OpenRPC discovery
|
||||||
|
|
||||||
|
* json-rpc-test-server: only serve regular files
|
||||||
|
|
||||||
|
* json-rpc-test-server: miscellaneous WebSocket fixes
|
||||||
|
|
||||||
|
|
||||||
|
1.1.0 (2020-10-13)
|
||||||
|
|
||||||
|
* Add method name tab completion using OpenRPC information
|
||||||
|
|
||||||
|
* Bind M-Enter to insert a newline into the command line
|
||||||
|
|
||||||
|
* json-rpc-test-server: fix a memory leak and request URI parsing
|
||||||
|
|
||||||
|
* Miscellaneous bug fixes
|
||||||
|
|
||||||
|
|
||||||
|
1.0.0 (2020-09-05)
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
|
||||||
63
README.adoc
63
README.adoc
@@ -2,53 +2,44 @@ json-rpc-shell
|
|||||||
==============
|
==============
|
||||||
:compact-option:
|
:compact-option:
|
||||||
|
|
||||||
'json-rpc-shell' is a simple shell for running JSON-RPC 2.0 queries.
|
'json-rpc-shell' is a shell for running JSON-RPC 2.0 queries.
|
||||||
|
|
||||||
This software has been created as a replacement for the following shell, which
|
This software was originally created as a replacement for
|
||||||
is written in Java: http://software.dzhuvinov.com/json-rpc-2.0-shell.html
|
http://software.dzhuvinov.com/json-rpc-2.0-shell.html[a different shell] made by
|
||||||
|
Vladimir Dzhuvinov, in order to avoid Java, but has evolved since.
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
In addition to most of the features provided by Vladimir Dzhuvinov's shell
|
In addition to most of the features provided by its predecessor, you will get
|
||||||
you get the following niceties:
|
the following niceties:
|
||||||
|
|
||||||
- configurable JSON syntax highlight, which with prettyprinting turned on
|
- configurable JSON syntax highlight, which with prettyprinting turned on
|
||||||
helps you make sense of the results significantly
|
helps you make sense of the results significantly
|
||||||
- ability to pipe output through a shell command, so that you can view the
|
- 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
|
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
|
- ability to edit the input line in your favourite editor as well with Alt+E
|
||||||
|
- WebSocket (RFC 6455) can also be used as a transport rather than HTTP
|
||||||
|
- even Language Server Protocol servers may be launched as a slave command
|
||||||
|
- support for method name tab completion using OpenRPC discovery or file input
|
||||||
|
|
||||||
Supported transports
|
Documentation
|
||||||
--------------------
|
-------------
|
||||||
- HTTP
|
See the link:json-rpc-shell.adoc[man page] for information about usage.
|
||||||
- HTTPS
|
The rest of this README will concern itself with externalities.
|
||||||
- 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).
|
|
||||||
|
|
||||||
Packages
|
Packages
|
||||||
--------
|
--------
|
||||||
Regular releases are sporadic. git master should be stable enough. You can get
|
Regular releases are sporadic. git master should be stable enough.
|
||||||
a package with the latest development version from Archlinux's AUR.
|
You can get a package with the latest development version using Arch Linux's
|
||||||
|
https://aur.archlinux.org/packages/json-rpc-shell-git[AUR],
|
||||||
|
or as a https://git.janouch.name/p/nixexprs[Nix derivation].
|
||||||
|
|
||||||
Building and Usage
|
Building
|
||||||
------------------
|
--------
|
||||||
Build dependencies: CMake, pkg-config, help2man,
|
Build dependencies: CMake, pkg-config, liberty (included),
|
||||||
liberty (included), http-parser (included) +
|
http-parser (included), asciidoctor or asciidoc (recommended but optional) +
|
||||||
Runtime dependencies: libev, Jansson, cURL, openssl,
|
Runtime dependencies:
|
||||||
readline or libedit >= 2013-07-12,
|
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.
|
|
||||||
|
|
||||||
$ git clone --recursive https://git.janouch.name/p/json-rpc-shell.git
|
$ git clone --recursive https://git.janouch.name/p/json-rpc-shell.git
|
||||||
$ mkdir json-rpc-shell/build
|
$ mkdir json-rpc-shell/build
|
||||||
@@ -65,16 +56,12 @@ Or you can try telling CMake to make a package for you. For Debian it is:
|
|||||||
$ cpack -G DEB
|
$ cpack -G DEB
|
||||||
# dpkg -i json-rpc-shell-*.deb
|
# dpkg -i json-rpc-shell-*.deb
|
||||||
|
|
||||||
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
|
Test server
|
||||||
-----------
|
-----------
|
||||||
If you install development packages for libmagic, an included test server will
|
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
|
be built but not installed which provides a trivial JSON-RPC 2.0 service with
|
||||||
FastCGI, SCGI, and WebSocket interfaces. It responds to the `ping` method.
|
FastCGI, SCGI, WebSocket and LSP-like co-process interfaces. It responds to
|
||||||
|
`ping` and `date`, supports OpenRPC discovery and it can serve static files.
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -5,14 +5,16 @@
|
|||||||
|
|
||||||
# Some distributions do add it, though
|
# Some distributions do add it, though
|
||||||
find_package (PkgConfig REQUIRED)
|
find_package (PkgConfig REQUIRED)
|
||||||
pkg_check_modules (LIBEV QUIET libev)
|
pkg_check_modules (LibEV QUIET libev)
|
||||||
|
|
||||||
if (NOT LIBEV_FOUND)
|
set (required_vars LibEV_LIBRARIES)
|
||||||
find_path (LIBEV_INCLUDE_DIRS ev.h)
|
if (NOT LibEV_FOUND)
|
||||||
find_library (LIBEV_LIBRARIES NAMES ev)
|
find_path (LibEV_INCLUDE_DIRS ev.h)
|
||||||
|
find_library (LibEV_LIBRARIES NAMES ev)
|
||||||
|
list (APPEND required_vars LibEV_INCLUDE_DIRS)
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
include (FindPackageHandleStandardArgs)
|
||||||
set (LIBEV_FOUND TRUE)
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS (LibEV DEFAULT_MSG ${required_vars})
|
||||||
endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES)
|
|
||||||
endif (NOT LIBEV_FOUND)
|
|
||||||
|
|
||||||
|
mark_as_advanced (LibEV_LIBRARIES LibEV_INCLUDE_DIRS)
|
||||||
|
|||||||
@@ -2,10 +2,12 @@
|
|||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#define PROGRAM_NAME "${PROJECT_NAME}"
|
#define PROGRAM_NAME "${PROJECT_NAME}"
|
||||||
#define PROGRAM_VERSION "${project_VERSION}"
|
#define PROGRAM_VERSION "${PROJECT_VERSION}"
|
||||||
|
|
||||||
#cmakedefine HAVE_READLINE
|
#cmakedefine HAVE_READLINE
|
||||||
#cmakedefine HAVE_EDITLINE
|
#cmakedefine HAVE_EDITLINE
|
||||||
|
|
||||||
|
#cmakedefine01 ICONV_ACCEPTS_TRANSLIT
|
||||||
|
|
||||||
#endif // ! CONFIG_H
|
#endif // ! CONFIG_H
|
||||||
|
|
||||||
|
|||||||
Submodule http-parser updated: 5d414fcb4b...ec8b5ee63f
196
json-rpc-shell.adoc
Normal file
196
json-rpc-shell.adoc
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
json-rpc-shell(1)
|
||||||
|
=================
|
||||||
|
:doctype: manpage
|
||||||
|
:manmanual: json-rpc-shell Manual
|
||||||
|
:mansource: json-rpc-shell {release-version}
|
||||||
|
|
||||||
|
Name
|
||||||
|
----
|
||||||
|
json-rpc-shell - a shell for JSON-RPC 2.0
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
*json-rpc-shell* [_OPTION_]... { _ENDPOINT_ | _COMMAND_ [_ARG_]... }
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
:colon: :
|
||||||
|
The _ENDPOINT_ must be either an HTTP or a WebSocket URL, with or without TLS
|
||||||
|
(i.e. one of the _http{colon}//_, _https{colon}//_, _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.
|
||||||
|
|
||||||
|
*-O*[__PATH__], *--openrpc*[**=**__PATH__]::
|
||||||
|
Call "rpc.discover" upon start-up in order to pull in OpenRPC data for
|
||||||
|
tab completion of method names. If a path is given, it is read from a file.
|
||||||
|
|
||||||
|
*-e*, *--execute*::
|
||||||
|
Rather than an _ENDPOINT_, accept a command line to execute and communicate
|
||||||
|
with using the JSON-RPC 2.0 protocol variation used in the Language Server
|
||||||
|
Protocol.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Environment
|
||||||
|
-----------
|
||||||
|
*VISUAL*, *EDITOR*::
|
||||||
|
The editor program to be launched by the M-e key binding.
|
||||||
|
If neither variable is set, it defaults to *vi*(1).
|
||||||
|
|
||||||
|
Files
|
||||||
|
-----
|
||||||
|
*json-rpc-shell* follows the XDG Base Directory Specification.
|
||||||
|
|
||||||
|
_~/.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, press either M-Enter or C-v C-j in order to insert
|
||||||
|
newlines.
|
||||||
|
|
||||||
|
WebSocket
|
||||||
|
~~~~~~~~~
|
||||||
|
The JSON-RPC 2.0 specification 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.
|
||||||
|
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 may exhibit some unexpected behaviour.
|
||||||
|
|
||||||
|
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
|
||||||
1539
json-rpc-shell.c
1539
json-rpc-shell.c
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* json-rpc-test-server.c: JSON-RPC 2.0 demo server
|
* json-rpc-test-server.c: JSON-RPC 2.0 demo server
|
||||||
*
|
*
|
||||||
* Copyright (c) 2015 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2015 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -329,6 +329,7 @@ fcgi_muxer_on_get_values
|
|||||||
nv_parser.output = &values;
|
nv_parser.output = &values;
|
||||||
|
|
||||||
fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
|
fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len);
|
||||||
|
fcgi_nv_parser_free (&nv_parser);
|
||||||
const char *key = NULL;
|
const char *key = NULL;
|
||||||
|
|
||||||
// No real-world servers seem to actually use multiplexing
|
// No real-world servers seem to actually use multiplexing
|
||||||
@@ -524,11 +525,11 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
// --- WebSockets --------------------------------------------------------------
|
// --- WebSocket ---------------------------------------------------------------
|
||||||
/// @defgroup WebSockets
|
/// @defgroup WebSocket
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
// WebSockets aren't CGI-compatible, therefore we must handle the initial HTTP
|
// WebSocket isn't CGI-compatible, therefore we must handle the initial HTTP
|
||||||
// handshake ourselves. Luckily it's not too much of a bother with http-parser.
|
// handshake ourselves. Luckily it's not too much of a bother with http-parser.
|
||||||
// Typically there will be a normal HTTP server in front of us, proxying the
|
// Typically there will be a normal HTTP server in front of us, proxying the
|
||||||
// requests based on the URI.
|
// requests based on the URI.
|
||||||
@@ -536,7 +537,7 @@ fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len)
|
|||||||
enum ws_handler_state
|
enum ws_handler_state
|
||||||
{
|
{
|
||||||
WS_HANDLER_CONNECTING, ///< Parsing HTTP
|
WS_HANDLER_CONNECTING, ///< Parsing HTTP
|
||||||
WS_HANDLER_OPEN, ///< Parsing WebSockets frames
|
WS_HANDLER_OPEN, ///< Parsing WebSocket frames
|
||||||
WS_HANDLER_CLOSING, ///< Partial closure by us
|
WS_HANDLER_CLOSING, ///< Partial closure by us
|
||||||
WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
|
WS_HANDLER_FLUSHING, ///< Just waiting for client EOF
|
||||||
WS_HANDLER_CLOSED ///< Dead, both sides closed
|
WS_HANDLER_CLOSED ///< Dead, both sides closed
|
||||||
@@ -850,6 +851,17 @@ ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents)
|
|||||||
self->close_cb (self, false /* half_close */);
|
self->close_cb (self, false /* half_close */);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool ws_handler_fail_handshake (struct ws_handler *self,
|
||||||
|
const char *status, ...) ATTRIBUTE_SENTINEL;
|
||||||
|
|
||||||
|
#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
|
||||||
|
#define HTTP_400_BAD_REQUEST "400 Bad Request"
|
||||||
|
#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
|
||||||
|
#define HTTP_408_REQUEST_TIMEOUT "408 Request Timeout"
|
||||||
|
#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
|
||||||
|
#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
|
||||||
|
#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
|
ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
|
||||||
{
|
{
|
||||||
@@ -857,13 +869,7 @@ ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents)
|
|||||||
(void) revents;
|
(void) revents;
|
||||||
struct ws_handler *self = watcher->data;
|
struct ws_handler *self = watcher->data;
|
||||||
|
|
||||||
// XXX: this is a no-op, since this currently doesn't even call shutdown
|
ws_handler_fail_handshake (self, HTTP_408_REQUEST_TIMEOUT, NULL);
|
||||||
// immediately but postpones it until later
|
|
||||||
self->close_cb (self, true /* half_close */);
|
|
||||||
self->state = WS_HANDLER_FLUSHING;
|
|
||||||
|
|
||||||
if (self->on_close)
|
|
||||||
self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "handshake timeout");
|
|
||||||
|
|
||||||
self->state = WS_HANDLER_CLOSED;
|
self->state = WS_HANDLER_CLOSED;
|
||||||
self->close_cb (self, false /* half_close */);
|
self->close_cb (self, false /* half_close */);
|
||||||
@@ -1002,9 +1008,10 @@ ws_handler_on_headers_complete (http_parser *parser)
|
|||||||
if (self->have_header_value)
|
if (self->have_header_value)
|
||||||
ws_handler_on_header_read (self);
|
ws_handler_on_header_read (self);
|
||||||
|
|
||||||
// We strictly require a protocol upgrade
|
// We require a protocol upgrade. 1 is for "skip body", 2 is the same
|
||||||
|
// + "stop processing", return another number to indicate a problem here.
|
||||||
if (!parser->upgrade)
|
if (!parser->upgrade)
|
||||||
return 2;
|
return 3;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1013,17 +1020,10 @@ static int
|
|||||||
ws_handler_on_url (http_parser *parser, const char *at, size_t len)
|
ws_handler_on_url (http_parser *parser, const char *at, size_t len)
|
||||||
{
|
{
|
||||||
struct ws_handler *self = parser->data;
|
struct ws_handler *self = parser->data;
|
||||||
str_append_data (&self->value, at, len);
|
str_append_data (&self->url, at, len);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols"
|
|
||||||
#define HTTP_400_BAD_REQUEST "400 Bad Request"
|
|
||||||
#define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed"
|
|
||||||
#define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed"
|
|
||||||
#define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required"
|
|
||||||
#define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported"
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ws_handler_http_responsev (struct ws_handler *self,
|
ws_handler_http_responsev (struct ws_handler *self,
|
||||||
const char *status, char *const *fields)
|
const char *status, char *const *fields)
|
||||||
@@ -1065,6 +1065,7 @@ ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...)
|
|||||||
struct strv v = strv_make ();
|
struct strv v = strv_make ();
|
||||||
while ((s = va_arg (ap, const char *)))
|
while ((s = va_arg (ap, const char *)))
|
||||||
strv_append (&v, s);
|
strv_append (&v, s);
|
||||||
|
strv_append (&v, "Connection: close");
|
||||||
|
|
||||||
va_end (ap);
|
va_end (ap);
|
||||||
ws_handler_http_responsev (self, status, v.vector);
|
ws_handler_http_responsev (self, status, v.vector);
|
||||||
@@ -1109,7 +1110,7 @@ ws_handler_finish_handshake (struct ws_handler *self)
|
|||||||
if (!connection || strcasecmp_ascii (connection, "Upgrade"))
|
if (!connection || strcasecmp_ascii (connection, "Upgrade"))
|
||||||
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
|
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
|
||||||
|
|
||||||
// Check if we can actually upgrade the protocol to WebSockets
|
// Check if we can actually upgrade the protocol to WebSocket
|
||||||
const char *upgrade = str_map_find (&self->headers, "Upgrade");
|
const char *upgrade = str_map_find (&self->headers, "Upgrade");
|
||||||
struct http_protocol *offered_upgrades = NULL;
|
struct http_protocol *offered_upgrades = NULL;
|
||||||
bool can_upgrade = false;
|
bool can_upgrade = false;
|
||||||
@@ -1267,11 +1268,13 @@ ws_handler_push (struct ws_handler *self, const void *data, size_t len)
|
|||||||
ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
|
ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher);
|
||||||
|
|
||||||
if (err == HPE_CB_headers_complete)
|
if (err == HPE_CB_headers_complete)
|
||||||
|
{
|
||||||
print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
|
print_debug ("WS handshake failed: %s", "missing `Upgrade' field");
|
||||||
else
|
FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED,
|
||||||
print_debug ("WS handshake failed: %s",
|
"Upgrade: websocket", SEC_WS_VERSION ": 13");
|
||||||
http_errno_description (err));
|
}
|
||||||
|
|
||||||
|
print_debug ("WS handshake failed: %s", http_errno_description (err));
|
||||||
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
|
FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -1285,7 +1288,7 @@ static struct simple_config_item g_config_table[] =
|
|||||||
{ "bind_host", NULL, "Address of the server" },
|
{ "bind_host", NULL, "Address of the server" },
|
||||||
{ "port_fastcgi", "9000", "Port to bind for FastCGI" },
|
{ "port_fastcgi", "9000", "Port to bind for FastCGI" },
|
||||||
{ "port_scgi", NULL, "Port to bind for SCGI" },
|
{ "port_scgi", NULL, "Port to bind for SCGI" },
|
||||||
{ "port_ws", NULL, "Port to bind for WebSockets" },
|
{ "port_ws", NULL, "Port to bind for WebSocket" },
|
||||||
{ "pid_file", NULL, "Full path for the PID file" },
|
{ "pid_file", NULL, "Full path for the PID file" },
|
||||||
// XXX: here belongs something like a web SPA that interfaces with us
|
// XXX: here belongs something like a web SPA that interfaces with us
|
||||||
{ "static_root", NULL, "The root for static content" },
|
{ "static_root", NULL, "The root for static content" },
|
||||||
@@ -1445,6 +1448,39 @@ json_rpc_handler_info_cmp (const void *first, const void *second)
|
|||||||
((struct json_rpc_handler_info *) second)->method_name);
|
((struct json_rpc_handler_info *) second)->method_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static json_t *
|
||||||
|
open_rpc_describe (const char *method, json_t *result)
|
||||||
|
{
|
||||||
|
return json_pack ("{sssoso}", "name", method, "params", json_pack ("[]"),
|
||||||
|
"result", json_pack ("{ssso}", "name", method, "schema", result));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This server rarely sees changes and we can afford to hardcode the schema
|
||||||
|
static json_t *
|
||||||
|
json_rpc_discover (struct server_context *ctx, json_t *params)
|
||||||
|
{
|
||||||
|
(void) ctx;
|
||||||
|
(void) params;
|
||||||
|
|
||||||
|
json_t *info = json_pack ("{ssss}",
|
||||||
|
"title", PROGRAM_NAME, "version", PROGRAM_VERSION);
|
||||||
|
json_t *methods = json_pack ("[oooo]",
|
||||||
|
open_rpc_describe ("date", json_pack ("{ssso}", "type", "object",
|
||||||
|
"properties", json_pack ("{s{ss}s{ss}s{ss}s{ss}s{ss}s{ss}}",
|
||||||
|
"year", "type", "number",
|
||||||
|
"month", "type", "number",
|
||||||
|
"day", "type", "number",
|
||||||
|
"hours", "type", "number",
|
||||||
|
"minutes", "type", "number",
|
||||||
|
"seconds", "type", "number"))),
|
||||||
|
open_rpc_describe ("ping", json_pack ("{ss}", "type", "string")),
|
||||||
|
open_rpc_describe ("rpc.discover", json_pack ("{ss}", "$ref",
|
||||||
|
"https://github.com/open-rpc/meta-schema/raw/master/schema.json")),
|
||||||
|
open_rpc_describe ("wait", json_pack ("{ss}", "type", "null")));
|
||||||
|
return json_rpc_response (NULL, json_pack ("{sssoso}",
|
||||||
|
"openrpc", "1.2.6", "info", info, "methods", methods), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static json_t *
|
static json_t *
|
||||||
json_rpc_ping (struct server_context *ctx, json_t *params)
|
json_rpc_ping (struct server_context *ctx, json_t *params)
|
||||||
{
|
{
|
||||||
@@ -1457,6 +1493,16 @@ json_rpc_ping (struct server_context *ctx, json_t *params)
|
|||||||
return json_rpc_response (NULL, json_string ("pong"), NULL);
|
return json_rpc_response (NULL, json_string ("pong"), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static json_t *
|
||||||
|
json_rpc_wait (struct server_context *ctx, json_t *params)
|
||||||
|
{
|
||||||
|
(void) ctx;
|
||||||
|
(void) params;
|
||||||
|
|
||||||
|
sleep (1);
|
||||||
|
return json_rpc_response (NULL, json_null (), NULL);
|
||||||
|
}
|
||||||
|
|
||||||
static json_t *
|
static json_t *
|
||||||
json_rpc_date (struct server_context *ctx, json_t *params)
|
json_rpc_date (struct server_context *ctx, json_t *params)
|
||||||
{
|
{
|
||||||
@@ -1486,8 +1532,10 @@ process_json_rpc_request (struct server_context *ctx, json_t *request)
|
|||||||
// Eventually it might be better to move this into a map in the context.
|
// Eventually it might be better to move this into a map in the context.
|
||||||
static struct json_rpc_handler_info handlers[] =
|
static struct json_rpc_handler_info handlers[] =
|
||||||
{
|
{
|
||||||
{ "date", json_rpc_date },
|
{ "date", json_rpc_date },
|
||||||
{ "ping", json_rpc_ping },
|
{ "ping", json_rpc_ping },
|
||||||
|
{ "rpc.discover", json_rpc_discover },
|
||||||
|
{ "wait", json_rpc_wait },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!json_is_object (request))
|
if (!json_is_object (request))
|
||||||
@@ -1544,7 +1592,6 @@ static void
|
|||||||
process_json_rpc (struct server_context *ctx,
|
process_json_rpc (struct server_context *ctx,
|
||||||
const void *data, size_t len, struct str *output)
|
const void *data, size_t len, struct str *output)
|
||||||
{
|
{
|
||||||
|
|
||||||
json_error_t e;
|
json_error_t e;
|
||||||
json_t *request;
|
json_t *request;
|
||||||
if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
|
if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e)))
|
||||||
@@ -1619,15 +1666,37 @@ struct request_handler
|
|||||||
LIST_HEADER (struct request_handler)
|
LIST_HEADER (struct request_handler)
|
||||||
|
|
||||||
/// Install ourselves as the handler for the request, if applicable.
|
/// Install ourselves as the handler for the request, if applicable.
|
||||||
|
/// If the request contains data, check it against CONTENT_LENGTH.
|
||||||
|
/// ("Transfer-Encoding: chunked" should be dechunked by the HTTP server,
|
||||||
|
/// however it is possible that it mishandles this situation.)
|
||||||
/// Sets @a continue_ to false if further processing should be stopped,
|
/// Sets @a continue_ to false if further processing should be stopped,
|
||||||
/// meaning the request has already been handled.
|
/// meaning the request has already been handled.
|
||||||
|
/// Note that starting the response before receiving all data denies you
|
||||||
|
/// the option of returning error status codes based on the data.
|
||||||
bool (*try_handle) (struct request *request,
|
bool (*try_handle) (struct request *request,
|
||||||
struct str_map *headers, bool *continue_);
|
struct str_map *headers, bool *continue_);
|
||||||
|
|
||||||
/// Handle incoming data. "len == 0" means EOF.
|
/// Handle incoming data. "len == 0" means EOF.
|
||||||
/// Returns false if there is no more processing to be done.
|
/// Returns false if there is no more processing to be done.
|
||||||
// FIXME: the EOF may or may not be delivered when request is cut short,
|
/// EOF is never delivered on a network error (see client_read_loop()).
|
||||||
// we should fix FastCGI not to deliver it on CONTENT_LENGTH mismatch
|
// XXX: the EOF may or may not be delivered when the request is cut short:
|
||||||
|
// - client_scgi delivers an EOF when it itself receives an EOF without
|
||||||
|
// considering any mismatch, and it can deliver another one earlier
|
||||||
|
// when the counter just goes down to 0... depends on what we return
|
||||||
|
// from here upon the first occasion (whether we want to close).
|
||||||
|
// - FCGI_ABORT_REQUEST /might/ not close the stdin and it /might/ cover
|
||||||
|
// a CONTENT_LENGTH mismatch, since this callback wouldn't get invoked.
|
||||||
|
// The FastCGI specification explicitly says to compare CONTENT_LENGTH
|
||||||
|
// against the number of received bytes, which may only be smaller.
|
||||||
|
//
|
||||||
|
// We might want to adjust client_scgi and client_fcgi to not invoke
|
||||||
|
// request_push(EOF) when CONTENT_LENGTH hasn't been reached and remove
|
||||||
|
// the extra EOF generation from client_scgi (why is it there, does the
|
||||||
|
// server keep the connection open, or is it just a precaution?)
|
||||||
|
//
|
||||||
|
// The finalization callback takes care of any needs to destruct data.
|
||||||
|
// If we handle this reliably in all clients, try_handle won't have to,
|
||||||
|
// as it will run in a stricter-than-CGI scenario.
|
||||||
bool (*push_cb) (struct request *request, const void *data, size_t len);
|
bool (*push_cb) (struct request *request, const void *data, size_t len);
|
||||||
|
|
||||||
/// Destroy the handler's data stored in the request object
|
/// Destroy the handler's data stored in the request object
|
||||||
@@ -1749,7 +1818,9 @@ request_handler_json_rpc_push
|
|||||||
|
|
||||||
// TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
|
// TODO: check buf.len against CONTENT_LENGTH; if it's less, then the
|
||||||
// client hasn't been successful in transferring all of its data.
|
// client hasn't been successful in transferring all of its data.
|
||||||
// See also comment on request_handler::push_cb.
|
// See also comment on request_handler::push_cb. For JSON-RPC, though,
|
||||||
|
// it shouldn't matter as an incomplete request will be invalid and
|
||||||
|
// clients have no reason to append unnecessary trailing bytes.
|
||||||
|
|
||||||
struct str response = str_make ();
|
struct str response = str_make ();
|
||||||
str_append (&response, "Status: 200 OK\n");
|
str_append (&response, "Status: 200 OK\n");
|
||||||
@@ -1866,8 +1937,13 @@ request_handler_static_try_handle
|
|||||||
char *path = xstrdup_printf ("%s%s", root, suffix);
|
char *path = xstrdup_printf ("%s%s", root, suffix);
|
||||||
print_debug ("trying to statically serve %s", path);
|
print_debug ("trying to statically serve %s", path);
|
||||||
|
|
||||||
// TODO: check that this is a regular file
|
|
||||||
FILE *fp = fopen (path, "rb");
|
FILE *fp = fopen (path, "rb");
|
||||||
|
struct stat st = {};
|
||||||
|
if (fp && !fstat (fileno (fp), &st) && !S_ISREG (st.st_mode))
|
||||||
|
{
|
||||||
|
fclose (fp);
|
||||||
|
fp = NULL;
|
||||||
|
}
|
||||||
if (!fp)
|
if (!fp)
|
||||||
{
|
{
|
||||||
struct str response = str_make ();
|
struct str response = str_make ();
|
||||||
@@ -1912,8 +1988,8 @@ request_handler_static_try_handle
|
|||||||
request_write (request, buf, len);
|
request_write (request, buf, len);
|
||||||
fclose (fp);
|
fclose (fp);
|
||||||
|
|
||||||
// TODO: this should rather not be returned all at once but in chunks;
|
// TODO: this should rather not be returned all at once but in chunks
|
||||||
// file read requests never return EAGAIN
|
// (consider Transfer-Encoding); file read requests never return EAGAIN
|
||||||
// TODO: actual file data should really be returned by a callback when
|
// TODO: actual file data should really be returned by a callback when
|
||||||
// the socket is writable with nothing to be sent (pumping the entire
|
// the socket is writable with nothing to be sent (pumping the entire
|
||||||
// file all at once won't really work if it's huge).
|
// file all at once won't really work if it's huge).
|
||||||
@@ -2047,6 +2123,8 @@ static void
|
|||||||
client_shutdown (struct client *self)
|
client_shutdown (struct client *self)
|
||||||
{
|
{
|
||||||
self->flushing = true;
|
self->flushing = true;
|
||||||
|
// In case this shutdown is immediately followed by a close, try our best
|
||||||
|
(void) flush_queue (&self->write_queue, self->socket_fd);
|
||||||
ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
|
ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2358,14 +2436,15 @@ client_scgi_on_content (void *user_data, const void *data, size_t len)
|
|||||||
print_debug ("SCGI request got more data than CONTENT_LENGTH");
|
print_debug ("SCGI request got more data than CONTENT_LENGTH");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// We're in a slight disagreement with the specification since
|
// We're in a slight disagreement with the SCGI specification since
|
||||||
// this tries to write output before it has read all the input
|
// this tries to write output before it has read all the input
|
||||||
if (!request_push (&self->request, data, len))
|
if (!request_push (&self->request, data, len))
|
||||||
return false;
|
return false;
|
||||||
|
if ((self->remaining_content -= len))
|
||||||
|
return true;
|
||||||
|
|
||||||
// Signalise end of input to the request handler
|
// Signalise end of input to the request handler
|
||||||
return (self->remaining_content -= len) != 0
|
return request_push (&self->request, NULL, 0);
|
||||||
|| request_push (&self->request, NULL, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
@@ -2418,12 +2497,12 @@ client_scgi_create (EV_P_ int sock_fd)
|
|||||||
return &self->client;
|
return &self->client;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- WebSockets client handler -----------------------------------------------
|
// --- WebSocket client handler ------------------------------------------------
|
||||||
|
|
||||||
struct client_ws
|
struct client_ws
|
||||||
{
|
{
|
||||||
struct client client; ///< Parent class
|
struct client client; ///< Parent class
|
||||||
struct ws_handler handler; ///< WebSockets connection handler
|
struct ws_handler handler; ///< WebSocket connection handler
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -2514,6 +2593,165 @@ client_ws_create (EV_P_ int sock_fd)
|
|||||||
return &self->client;
|
return &self->client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Co-process client -------------------------------------------------------
|
||||||
|
|
||||||
|
// This is mostly copied over from json-rpc-shell.c, only a bit simplified.
|
||||||
|
// We're giving up on header parsing in order to keep this small.
|
||||||
|
struct co_context
|
||||||
|
{
|
||||||
|
struct server_context *ctx; ///< Server context
|
||||||
|
struct str message; ///< Message data
|
||||||
|
struct http_parser parser; ///< HTTP parser
|
||||||
|
bool pending_fake_starter; ///< Start of message?
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
client_co_on_message_begin (http_parser *parser)
|
||||||
|
{
|
||||||
|
struct co_context *self = parser->data;
|
||||||
|
str_reset (&self->message);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
client_co_on_body (http_parser *parser, const char *at, size_t len)
|
||||||
|
{
|
||||||
|
struct co_context *self = parser->data;
|
||||||
|
str_append_data (&self->message, at, len);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
client_co_on_message_complete (http_parser *parser)
|
||||||
|
{
|
||||||
|
struct co_context *self = parser->data;
|
||||||
|
http_parser_pause (&self->parser, true);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The LSP incorporates a very thin subset of RFC 822, and it so happens
|
||||||
|
// that we may simply reuse the full HTTP parser here, with a small hack.
|
||||||
|
static const http_parser_settings client_co_http_settings =
|
||||||
|
{
|
||||||
|
.on_message_begin = client_co_on_message_begin,
|
||||||
|
.on_body = client_co_on_body,
|
||||||
|
.on_message_complete = client_co_on_message_complete,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_respond (const struct str *buf)
|
||||||
|
{
|
||||||
|
struct str wrapped = str_make();
|
||||||
|
str_append_printf (&wrapped,
|
||||||
|
"Content-Length: %zu\r\n"
|
||||||
|
"Content-Type: application/json; charset=utf-8\r\n"
|
||||||
|
"\r\n", buf->len);
|
||||||
|
str_append_data (&wrapped, buf->str, buf->len);
|
||||||
|
|
||||||
|
if (write (STDOUT_FILENO, wrapped.str, wrapped.len)
|
||||||
|
!= (ssize_t) wrapped.len)
|
||||||
|
exit_fatal ("write: %s", strerror (errno));
|
||||||
|
str_free (&wrapped);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_inject_starter (struct co_context *self)
|
||||||
|
{
|
||||||
|
// The default "Connection: keep-alive" maps well here.
|
||||||
|
// We cannot feed this line into the parser from within callbacks.
|
||||||
|
static const char starter[] = "POST / HTTP/1.1\r\n";
|
||||||
|
http_parser_pause (&self->parser, false);
|
||||||
|
|
||||||
|
size_t n_parsed = http_parser_execute (&self->parser,
|
||||||
|
&client_co_http_settings, starter, sizeof starter - 1);
|
||||||
|
enum http_errno err = HTTP_PARSER_ERRNO (&self->parser);
|
||||||
|
if (n_parsed != sizeof starter - 1 || err != HPE_OK)
|
||||||
|
exit_fatal ("protocol failure: %s", http_errno_description (err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_process (struct co_context *self)
|
||||||
|
{
|
||||||
|
struct str *message = &self->message;
|
||||||
|
struct str response = str_make ();
|
||||||
|
process_json_rpc (self->ctx, message->str, message->len, &response);
|
||||||
|
if (response.len)
|
||||||
|
client_co_respond (&response);
|
||||||
|
str_free (&response);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_parse (struct co_context *self, const char *data, size_t len,
|
||||||
|
size_t *n_parsed)
|
||||||
|
{
|
||||||
|
if (self->pending_fake_starter)
|
||||||
|
{
|
||||||
|
self->pending_fake_starter = false;
|
||||||
|
client_co_inject_starter (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
*n_parsed = http_parser_execute
|
||||||
|
(&self->parser, &client_co_http_settings, data, len);
|
||||||
|
if (self->parser.upgrade)
|
||||||
|
exit_fatal ("protocol failure: %s", "unsupported upgrade attempt");
|
||||||
|
|
||||||
|
enum http_errno err = HTTP_PARSER_ERRNO (&self->parser);
|
||||||
|
if (err == HPE_PAUSED)
|
||||||
|
{
|
||||||
|
self->pending_fake_starter = true;
|
||||||
|
client_co_process (self);
|
||||||
|
}
|
||||||
|
else if (err != HPE_OK)
|
||||||
|
exit_fatal ("protocol failure: %s", http_errno_description (err));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_on_data (struct co_context *self, const char *data, size_t len)
|
||||||
|
{
|
||||||
|
size_t n_parsed = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
client_co_parse (self, data, len, &n_parsed);
|
||||||
|
data += n_parsed;
|
||||||
|
}
|
||||||
|
while ((len -= n_parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_co_run (struct server_context *ctx)
|
||||||
|
{
|
||||||
|
struct co_context self = {};
|
||||||
|
self.ctx = ctx;
|
||||||
|
self.message = str_make ();
|
||||||
|
http_parser_init (&self.parser, HTTP_REQUEST);
|
||||||
|
self.parser.data = &self;
|
||||||
|
self.pending_fake_starter = true;
|
||||||
|
|
||||||
|
hard_assert (set_blocking (STDIN_FILENO, false));
|
||||||
|
struct str buf = str_make ();
|
||||||
|
struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN };
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (poll (&pfd, 1, -1) <= 0)
|
||||||
|
exit_fatal ("poll: %s", strerror (errno));
|
||||||
|
|
||||||
|
str_remove_slice (&buf, 0, buf.len);
|
||||||
|
enum socket_io_result result = socket_io_try_read (pfd.fd, &buf);
|
||||||
|
int errno_saved = errno;
|
||||||
|
|
||||||
|
if (buf.len)
|
||||||
|
client_co_on_data (&self, buf.str, buf.len);
|
||||||
|
if (result == SOCKET_IO_ERROR)
|
||||||
|
exit_fatal ("read: %s", strerror (errno_saved));
|
||||||
|
if (result == SOCKET_IO_EOF)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
str_free (&buf);
|
||||||
|
str_free (&self.message);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Basic server stuff ------------------------------------------------------
|
// --- Basic server stuff ------------------------------------------------------
|
||||||
|
|
||||||
typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
|
typedef struct client *(*client_create_fn) (EV_P_ int sock_fd);
|
||||||
@@ -2877,11 +3115,12 @@ daemonize (struct server_context *ctx)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
parse_program_arguments (int argc, char **argv)
|
parse_program_arguments (int argc, char **argv, bool *running_as_slave)
|
||||||
{
|
{
|
||||||
static const struct opt opts[] =
|
static const struct opt opts[] =
|
||||||
{
|
{
|
||||||
{ 't', "test", NULL, 0, "self-test" },
|
{ 't', "test", NULL, 0, "self-test" },
|
||||||
|
{ 's', "slave", NULL, 0, "co-process mode" },
|
||||||
{ 'd', "debug", NULL, 0, "run in debug mode" },
|
{ 'd', "debug", NULL, 0, "run in debug mode" },
|
||||||
{ 'h', "help", NULL, 0, "display this help and exit" },
|
{ 'h', "help", NULL, 0, "display this help and exit" },
|
||||||
{ 'V', "version", NULL, 0, "output version information and exit" },
|
{ 'V', "version", NULL, 0, "output version information and exit" },
|
||||||
@@ -2901,6 +3140,9 @@ parse_program_arguments (int argc, char **argv)
|
|||||||
case 't':
|
case 't':
|
||||||
test_main (argc, argv);
|
test_main (argc, argv);
|
||||||
exit (EXIT_SUCCESS);
|
exit (EXIT_SUCCESS);
|
||||||
|
case 's':
|
||||||
|
*running_as_slave = true;
|
||||||
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
g_debug_mode = true;
|
g_debug_mode = true;
|
||||||
break;
|
break;
|
||||||
@@ -2933,7 +3175,8 @@ parse_program_arguments (int argc, char **argv)
|
|||||||
int
|
int
|
||||||
main (int argc, char *argv[])
|
main (int argc, char *argv[])
|
||||||
{
|
{
|
||||||
parse_program_arguments (argc, argv);
|
bool running_as_a_slave = false;
|
||||||
|
parse_program_arguments (argc, argv, &running_as_a_slave);
|
||||||
|
|
||||||
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
|
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
|
||||||
|
|
||||||
@@ -2948,6 +3191,15 @@ main (int argc, char *argv[])
|
|||||||
exit (EXIT_FAILURE);
|
exit (EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There's a lot of unnecessary left-over scaffolding in this program,
|
||||||
|
// for testing purposes assume that everything is synchronous
|
||||||
|
if (running_as_a_slave)
|
||||||
|
{
|
||||||
|
client_co_run (&ctx);
|
||||||
|
server_context_free (&ctx);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
struct ev_loop *loop;
|
struct ev_loop *loop;
|
||||||
if (!(loop = EV_DEFAULT))
|
if (!(loop = EV_DEFAULT))
|
||||||
exit_fatal ("libev initialization failed");
|
exit_fatal ("libev initialization failed");
|
||||||
|
|||||||
2
liberty
2
liberty
Submodule liberty updated: 1a76b2032e...1930f138d4
Reference in New Issue
Block a user