Permalink
Browse files

=== version 0.5 (09/06/2009) - OTP compliant application: supervisors…

…, .app file, etc.
- logger server
- updated scripts for starting the server from command line
- improved compilation scripts
  • Loading branch information...
1 parent e48a9fe commit 3ed39f93d23a0799fa02165a1b727aaf835932e2 Antonio Garrote committed Sep 6, 2009
Showing with 5,181 additions and 16 deletions.
  1. +7 −0 CHANGELOG
  2. +12 −0 README
  3. +10 −4 Rakefile
  4. +71 −0 contrib/erlang-rfc4627/dist/Makefile
  5. +36 −0 contrib/erlang-rfc4627/dist/include/rfc4627.hrl
  6. +31 −0 contrib/erlang-rfc4627/dist/include/rfc4627_jsonrpc.hrl
  7. BIN contrib/erlang-rfc4627/dist/rfc4627_jsonrpc.ez
  8. +625 −0 contrib/erlang-rfc4627/dist/src/rfc4627.erl
  9. +554 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc.erl
  10. +36 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_app.erl
  11. +172 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_http.erl
  12. +159 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_inets.erl
  13. +106 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_mochiweb.erl
  14. +109 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_registry.erl
  15. +42 −0 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_sup.erl
  16. +15 −0 contrib/erlang-rfc4627/dist/test/server_root/conf/httpd.conf
  17. +465 −0 contrib/erlang-rfc4627/dist/test/server_root/conf/mime.types
  18. +17 −0 contrib/erlang-rfc4627/dist/test/server_root/htdocs/index.html
  19. +381 −0 contrib/erlang-rfc4627/dist/test/server_root/htdocs/json.js
  20. +125 −0 contrib/erlang-rfc4627/dist/test/server_root/htdocs/jsonrpc.js
  21. +1,781 −0 contrib/erlang-rfc4627/dist/test/server_root/htdocs/prototype-1.4.0.js
  22. +14 −0 contrib/erlang-rfc4627/dist/test/server_root/htdocs/test-client.js
  23. +73 −0 contrib/erlang-rfc4627/dist/test/test_jsonrpc_inets.erl
  24. +187 −0 contrib/erlang-rfc4627/dist/test/test_rfc4627.erl
  25. +17 −0 examples/client_data.rb
  26. +18 −0 examples/worker_data.rb
  27. +13 −1 src/client_proxy.erl
  28. +16 −2 src/connections.erl
  29. +12 −0 src/egearmand.app
  30. +12 −1 src/functions_registry.erl
  31. +1 −1 src/gearmand.erl
  32. +14 −1 src/jobs_queue_server.erl
  33. +18 −3 src/log.erl
  34. +12 −1 src/mnesia_store.erl
  35. +11 −1 src/rabbit_backend.erl
  36. +9 −1 src/worker_proxy.erl
View
7 CHANGELOG
@@ -1,3 +1,10 @@
+=== version 0.5 (09/06/2009)
+
+- OTP compliant application: supervisors, .app file, etc.
+- logger server
+- updated scripts for starting the server from command line
+- improved compilation scripts
+
=== version 0.4 (09/06/2009)
- Changelog
View
12 README
@@ -9,6 +9,13 @@ Erlang (tested with OTP R13B01),
Ruby and Rake for compiling
erlang-rfc4627 (included in the contrib directory)
+=== OTP application support
+
+The server can be started as an OTP application, try.
+> application:start(egearmand) .
+You can change the configuration of the application editing the
+file ebin/egearmand.app, editing the host, port, and log mechanism.
+
=== extensions
From version 0.3 the server has initial support for extensions.
@@ -41,3 +48,8 @@ $rake
=== running
./egearmand [-host host] [-port port] [-log path] [-level (debug | info | warning | error)]
+
+=== examples
+
+The distribution includes some examples of clients and workers using to test the server and the RabbitMQ extension.
+The examples are coded in Ruby, and use the xing-gearman-ruby gem to work. You can obtain it from github: git://github.com/xing/gearman-ruby.git
View
14 Rakefile
@@ -7,7 +7,7 @@ ERLC_FLAGS = "-I#{INCLUDE} +warn_unused_vars +warn_unused_import +debug_info "
SRC = FileList['src/**/*.erl']
OBJ = SRC.pathmap("%{ebin}X.beam")
-CLEAN.include("ebin/*.beam")
+CLEAN.include("ebin/*")
directory 'ebin'
@@ -25,8 +25,14 @@ end
task :compile => ['ebin'] + OBJ
-task :copy_records do
- sh "cp src/*.hrl ebin/"
+task :deps do
+ sh "cd contrib/erlang-rfc4627/ && make"
+ sh "cp contrib/erlang-rfc4627/ebin/*.beam ebin/"
end
-task :default => [:compile, :copy_records]
+task :copy do
+ sh "cp src/*.hrl ebin/"
+ sh "cp src/*.app ebin/"
+end
+
+task :default => [:compile, :deps, :copy]
View
71 contrib/erlang-rfc4627/dist/Makefile
@@ -0,0 +1,71 @@
+SOURCE_DIR=src
+EBIN_DIR=ebin
+DOC_DIR=doc
+INCLUDE_DIR=include
+INCLUDES=$(wildcard $(INCLUDE_DIR)/*.hrl)
+SOURCES=$(wildcard $(SOURCE_DIR)/*.erl)
+TARGETS=$(patsubst $(SOURCE_DIR)/%.erl, $(EBIN_DIR)/%.beam,$(SOURCES))
+ERLC_OPTS=-I $(INCLUDE_DIR) -o $(EBIN_DIR) -Wall +debug_info # +native -v
+DIST_DIR=dist
+SIGNING_KEY_ID=F8D7D525
+VERSION=1.2.0
+PACKAGE_NAME=rfc4627_jsonrpc
+EZ_NAME=$(PACKAGE_NAME).ez
+
+ifeq ($(shell uname -s),Darwin)
+SED=gnused
+else
+SED=sed
+endif
+
+all: package
+
+$(EBIN_DIR)/%.beam: $(SOURCE_DIR)/%.erl $(INCLUDES)
+ erlc $(ERLC_OPTS) $<
+
+clean:
+ rm -f ebin/*.beam
+ rm -f $(TARGETS)
+ rm -rf $(DIST_DIR)
+
+cleandoc:
+ rm -f doc/*
+
+dist: $(TARGETS)
+ mkdir -p $(DIST_DIR)
+ cp -r doc ebin include src test Makefile $(DIST_DIR)
+
+package: $(DIST_DIR)/$(PACKAGE).ez
+$(DIST_DIR)/$(PACKAGE).ez: $(TARGETS) dist
+ mkdir -p $(DIST_DIR)/$(PACKAGE_NAME)
+ cp -r $(DIST_DIR)/$(EBIN_DIR) $(DIST_DIR)/$(PACKAGE_NAME)
+ (cd $(DIST_DIR); zip -r $(EZ_NAME) $(PACKAGE_NAME))
+
+distclean: clean
+ rm -rf $(DIST_DIR)
+ find . -name '*~' -exec rm {} \;
+
+debian-package: clean
+ @(cat debian/changelog | head -1 | fgrep -q 'rfc4627-erlang ($(VERSION))') || \
+ (echo "No changelog entry for version $(VERSION) exists. Aborting."; false)
+ tar -cf debian-package.tar `ls | grep -v _darcs`
+ mkdir build
+ cd build; tar -xf ../debian-package.tar
+ cd build; dpkg-buildpackage -rfakeroot -k$(SIGNING_KEY_ID)
+ rm -rf build debian-package.tar
+
+.PHONY: doc
+doc: doc/overview.edoc
+ erl -noshell \
+ -eval 'edoc:application(rfc4627, ".", [])' \
+ -run init stop
+ $(SED) -e 's:\(<p><i>Generated by EDoc\), .*\(</i></p>\):\1\2:' -i doc/*.html
+
+doc/overview.edoc: src/overview.edoc.in
+ sed -e 's:%%VERSION%%:$(VERSION):g' < $< > $@
+
+test-compile:
+ erlc $(ERLC_OPTS) $(wildcard test/*.erl)
+
+test: all test-compile
+ erl -pa ebin -noshell -eval 'passed = test_rfc4627:test_all(), c:q().'
View
36 contrib/erlang-rfc4627/dist/include/rfc4627.hrl
@@ -0,0 +1,36 @@
+%% JSON - RFC 4627 - for Erlang
+%%---------------------------------------------------------------------------
+%% Copyright (c) 2007 Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% Copyright (c) 2007 LShift Ltd. <query@lshift.net>
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%%
+%% Convenience macros for encoding and decoding record structures.
+%%
+%% Erlang's compile-time-only notion of record definitions means we
+%% have to supply a constant record name in the source text.
+
+-define(RFC4627_FROM_RECORD(RName, R),
+ rfc4627:from_record(R, RName, record_info(fields, RName))).
+
+-define(RFC4627_TO_RECORD(RName, R),
+ rfc4627:to_record(R, #RName{}, record_info(fields, RName))).
View
31 contrib/erlang-rfc4627/dist/include/rfc4627_jsonrpc.hrl
@@ -0,0 +1,31 @@
+%% JSON-RPC for Erlang
+%%---------------------------------------------------------------------------
+%% Copyright (c) 2007 Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% Copyright (c) 2007 LShift Ltd. <query@lshift.net>
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%%
+%% Records for JSON-RPC services using the rfc4627_jsonrpc module.
+
+-record(service, {handler, name, id, version, summary, help, procs}).
+-record(service_proc, {name, summary, help, idempotent = false, params, return}).
+-record(service_proc_param, {name, type}).
View
BIN contrib/erlang-rfc4627/dist/rfc4627_jsonrpc.ez
Binary file not shown.
View
625 contrib/erlang-rfc4627/dist/src/rfc4627.erl
@@ -0,0 +1,625 @@
+%% JSON - RFC 4627 - for Erlang
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%%
+%% @reference <a href="http://www.ietf.org/rfc/rfc4627.txt">RFC
+%% 4627</a>, the JSON RFC
+%%
+%% @reference <a href="http://www.json.org/">JSON in general</a>
+%%
+%% @reference Joe Armstrong's <a
+%% href="http://www.erlang.org/ml-archive/erlang-questions/200511/msg00193.html">
+%% message</a> describing the basis of the JSON data type mapping that
+%% this module uses
+%%
+%% @doc An implementation of RFC 4627 (JSON, the JavaScript Object Notation) for Erlang.
+%%
+%% The basic API is comprised of the {@link encode/1} and {@link decode/1} functions.
+%%
+%% == Data Type Mapping ==
+%%
+%% The data type mapping I've implemented is as per Joe Armstrong's
+%% message [http://www.erlang.org/ml-archive/erlang-questions/200511/msg00193.html] - see {@link json()}.
+%%
+%% == Unicode ==
+%%
+%% When serializing a string, if characters are found with codepoint
+%% >127, we rely on the unicode encoder to build the proper byte
+%% sequence for transmission. We still use the \uXXXX escape for
+%% control characters (other than the RFC-specified specially
+%% recognised ones).
+%%
+%% {@link decode/1} will autodetect the unicode encoding used, and any
+%% strings returned in the result as binaries will contain UTF-8
+%% encoded byte sequences for codepoints >127. Object keys containing
+%% codepoints >127 will be returned as lists of codepoints, rather
+%% than being UTF-8 encoded. If you have already transformed the text
+%% to parse into a list of unicode codepoints, perhaps by your own use
+%% of {@link unicode_decode/1}, then use {@link decode_noauto/1} to
+%% avoid redundant and erroneous double-unicode-decoding.
+%%
+%% Similarly, {@link encode/1} produces text that is already UTF-8
+%% encoded. To get raw codepoints, use {@link encode_noauto/1} and
+%% {@link encode_noauto/2}. You can use {@link unicode_encode/1} to
+%% UTF-encode the results, if that's appropriate for your application.
+%%
+%% == Differences to the specification ==
+%%
+%% I'm lenient in the following ways during parsing:
+%%
+%% <ul>
+%% <li>repeated commas in arrays and objects collapse to a single comma</li>
+%% <li>any character =&lt;32 is considered whitespace</li>
+%% <li>leading zeros for numbers are accepted</li>
+%% <li>we don't restrict the toplevel token to only object or array -
+%% any JSON value can be used at toplevel</li>
+%% </ul>
+
+%% @type json() = jsonobj() | jsonarray() | jsonnum() | jsonstr() | true | false | null. An Erlang representation of a general JSON value.
+%% @type jsonobj() = {obj, [{jsonkey(), json()}]}. A JSON "object" or "struct".
+%% @type jsonkey() = string(). A field-name within a JSON "object".
+%% @type jsonarray() = [json()]. A JSON array value.
+%% @type jsonnum() = integer() | float(). A JSON numeric value.
+%% @type jsonstr() = binary(). A JSON string value.
+%% @type byte() = integer(). An integer >=0 and =&lt;255.
+
+-module(rfc4627).
+
+-export([mime_type/0, encode/1, decode/1]).
+-export([encode_noauto/1, encode_noauto/2, decode_noauto/1]).
+-export([unicode_decode/1, unicode_encode/1]).
+-export([from_record/3, to_record/3]).
+-export([hex_digit/1, digit_hex/1]).
+-export([get_field/2, get_field/3, set_field/3]).
+-export([equiv/2]).
+
+%% @spec () -> string()
+%% @doc Returns the IANA-registered MIME type for JSON data.
+mime_type() ->
+ "application/json".
+
+%% @spec (json()) -> [byte()]
+%%
+%% @doc Encodes the JSON value supplied, first into Unicode
+%% codepoints, and then into UTF-8.
+%%
+%% The resulting string is a list of byte values that should be
+%% interpreted as UTF-8 encoded text.
+%%
+%% During encoding, atoms and binaries are accepted as keys of JSON
+%% objects (type {@link jsonkey()}) as well as the usual strings
+%% (lists of character codepoints).
+encode(X) ->
+ unicode_encode({'utf-8', encode_noauto(X)}).
+
+%% @spec (json()) -> string()
+%%
+%% @doc Encodes the JSON value supplied into raw Unicode codepoints.
+%%
+%% The resulting string may contain codepoints with value >=128. You
+%% can use {@link unicode_encode/1} to UTF-encode the results, if
+%% that's appropriate for your application.
+%%
+%% During encoding, atoms and binaries are accepted as keys of JSON
+%% objects (type {@link jsonkey()}) as well as the usual strings
+%% (lists of character codepoints).
+encode_noauto(X) ->
+ lists:reverse(encode_noauto(X, [])).
+
+%% @spec (json(), string()) -> string()
+%%
+%% @doc As {@link encode_noauto/1}, but prepends <i>reversed</i> text
+%% to the supplied accumulator string.
+encode_noauto(true, Acc) ->
+ "eurt" ++ Acc;
+encode_noauto(false, Acc) ->
+ "eslaf" ++ Acc;
+encode_noauto(null, Acc) ->
+ "llun" ++ Acc;
+encode_noauto(Str, Acc) when is_binary(Str) ->
+ Codepoints = xmerl_ucs:from_utf8(Str),
+ quote_and_encode_string(Codepoints, Acc);
+encode_noauto(Str, Acc) when is_atom(Str) ->
+ quote_and_encode_string(atom_to_list(Str), Acc);
+encode_noauto(Num, Acc) when is_number(Num) ->
+ encode_number(Num, Acc);
+encode_noauto({obj, Fields}, Acc) ->
+ "}" ++ encode_object(Fields, "{" ++ Acc);
+encode_noauto(Dict, Acc) when element(1, Dict) =:= dict ->
+ "}" ++ encode_object(dict:to_list(Dict), "{" ++ Acc);
+encode_noauto(Arr, Acc) when is_list(Arr) ->
+ "]" ++ encode_array(Arr, "[" ++ Acc).
+
+encode_object([], Acc) ->
+ Acc;
+encode_object([{Key, Value}], Acc) ->
+ encode_field(Key, Value, Acc);
+encode_object([{Key, Value} | Rest], Acc) ->
+ encode_object(Rest, "," ++ encode_field(Key, Value, Acc)).
+
+encode_field(Key, Value, Acc) when is_binary(Key) ->
+ Codepoints = xmerl_ucs:from_utf8(Key),
+ encode_noauto(Value, ":" ++ quote_and_encode_string(Codepoints, Acc));
+encode_field(Key, Value, Acc) when is_atom(Key) ->
+ encode_noauto(Value, ":" ++ quote_and_encode_string(atom_to_list(Key), Acc));
+encode_field(Key, Value, Acc) when is_list(Key) ->
+ encode_noauto(Value, ":" ++ quote_and_encode_string(Key, Acc)).
+
+encode_array([], Acc) ->
+ Acc;
+encode_array([X], Acc) ->
+ encode_noauto(X, Acc);
+encode_array([X | Rest], Acc) ->
+ encode_array(Rest, "," ++ encode_noauto(X, Acc)).
+
+quote_and_encode_string(Str, Acc) ->
+ "\"" ++ encode_string(Str, "\"" ++ Acc).
+
+encode_string([], Acc) ->
+ Acc;
+encode_string([$" | Rest], Acc) ->
+ encode_string(Rest, [$", $\\ | Acc]);
+encode_string([$\\ | Rest], Acc) ->
+ encode_string(Rest, [$\\, $\\ | Acc]);
+encode_string([X | Rest], Acc) when X < 32 orelse X > 127 ->
+ encode_string(Rest, encode_general_char(X, Acc));
+encode_string([X | Rest], Acc) ->
+ encode_string(Rest, [X | Acc]).
+
+encode_general_char(8, Acc) -> [$b, $\\ | Acc];
+encode_general_char(9, Acc) -> [$t, $\\ | Acc];
+encode_general_char(10, Acc) -> [$n, $\\ | Acc];
+encode_general_char(12, Acc) -> [$f, $\\ | Acc];
+encode_general_char(13, Acc) -> [$r, $\\ | Acc];
+encode_general_char(X, Acc) when X > 127 -> [X | Acc];
+encode_general_char(X, Acc) ->
+ %% FIXME currently this branch never runs.
+ %% We could make it configurable, maybe?
+ Utf16Bytes = xmerl_ucs:to_utf16be(X),
+ encode_utf16be_chars(Utf16Bytes, Acc).
+
+encode_utf16be_chars([], Acc) ->
+ Acc;
+encode_utf16be_chars([B1, B2 | Rest], Acc) ->
+ encode_utf16be_chars(Rest, [hex_digit((B2) band 16#F),
+ hex_digit((B2 bsr 4) band 16#F),
+ hex_digit((B1) band 16#F),
+ hex_digit((B1 bsr 4) band 16#F),
+ $u,
+ $\\ | Acc]).
+
+%% @spec (Nibble::integer()) -> char()
+%% @doc Returns the character code corresponding to Nibble.
+%%
+%% Nibble must be >=0 and =&lt;16.
+hex_digit(0) -> $0;
+hex_digit(1) -> $1;
+hex_digit(2) -> $2;
+hex_digit(3) -> $3;
+hex_digit(4) -> $4;
+hex_digit(5) -> $5;
+hex_digit(6) -> $6;
+hex_digit(7) -> $7;
+hex_digit(8) -> $8;
+hex_digit(9) -> $9;
+hex_digit(10) -> $A;
+hex_digit(11) -> $B;
+hex_digit(12) -> $C;
+hex_digit(13) -> $D;
+hex_digit(14) -> $E;
+hex_digit(15) -> $F.
+
+encode_number(Num, Acc) when is_integer(Num) ->
+ lists:reverse(integer_to_list(Num), Acc);
+encode_number(Num, Acc) when is_float(Num) ->
+ lists:reverse(float_to_list(Num), Acc).
+
+%% @spec (Input::(binary() | [byte()])) -> ({ok, json(), Remainder} | {error, Reason})
+%% where Remainder = string()
+%% Reason = any()
+%%
+%% @doc Decodes a JSON value from an input binary or string of
+%% Unicode-encoded text.
+%%
+%% Given a binary, converts it to a list of bytes. Given a
+%% list/string, interprets it as a list of bytes.
+%%
+%% Uses {@link unicode_decode/1} on its input, which results in a list
+%% of codepoints, and then decodes a JSON value from that list of
+%% codepoints.
+%%
+%% Returns either `{ok, Result, Remainder}', where Remainder is the
+%% remaining portion of the input that was not consumed in the process
+%% of decoding Result, or `{error, Reason}'.
+decode(Bin) when is_binary(Bin) ->
+ decode(binary_to_list(Bin));
+decode(Bytes) ->
+ {_Charset, Codepoints} = unicode_decode(Bytes),
+ decode_noauto(Codepoints).
+
+%% @spec (Input::string()) -> ({ok, json(), string()} | {error, any()})
+%%
+%% @doc As {@link decode/1}, but does not perform Unicode decoding on its input.
+%%
+%% Expects a list of codepoints - an ordinary Erlang string - rather
+%% than a list of Unicode-encoded bytes.
+decode_noauto(Bin) when is_binary(Bin) ->
+ decode_noauto(binary_to_list(Bin));
+decode_noauto(Chars) ->
+ case catch parse(skipws(Chars)) of
+ {'EXIT', Reason} ->
+ %% Reason is usually far too much information, but helps
+ %% if needing to debug this module.
+ {error, Reason};
+ {Value, Remaining} ->
+ {ok, Value, skipws(Remaining)}
+ end.
+
+%% @spec ([byte()]) -> [char()]
+%%
+%% @doc Autodetects and decodes using the Unicode encoding of its input.
+%%
+%% From RFC4627, section 3, "Encoding":
+%%
+%% <blockquote>
+%% JSON text SHALL be encoded in Unicode. The default encoding is
+%% UTF-8.
+%%
+%% Since the first two characters of a JSON text will always be ASCII
+%% characters [RFC0020], it is possible to determine whether an octet
+%% stream is UTF-8, UTF-16 (BE or LE), or UTF-32 (BE or LE) by looking
+%% at the pattern of nulls in the first four octets.
+%%
+%% 00 00 00 xx UTF-32BE
+%% 00 xx 00 xx UTF-16BE
+%% xx 00 00 00 UTF-32LE
+%% xx 00 xx 00 UTF-16LE
+%% xx xx xx xx UTF-8
+%% </blockquote>
+%%
+%% Interestingly, the BOM (byte-order mark) is not mentioned. We
+%% support it here by using it to detect our encoding, discarding it
+%% if present, even though RFC4627 explicitly notes that the first two
+%% characters of a JSON text will be ASCII.
+%%
+%% If a BOM ([http://unicode.org/faq/utf_bom.html]) is present, we use
+%% that; if not, we use RFC4627's rules (as above). Note that UTF-32
+%% is the same as UCS-4 for our purposes (but see also
+%% [http://unicode.org/reports/tr19/tr19-9.html]). Note that UTF-16 is
+%% not the same as UCS-2!
+%%
+%% Note that I'm using xmerl's UCS/UTF support here. There's another
+%% UTF-8 codec in asn1rt, which works on binaries instead of lists.
+%%
+unicode_decode([0,0,254,255|C]) -> {'utf-32', xmerl_ucs:from_ucs4be(C)};
+unicode_decode([255,254,0,0|C]) -> {'utf-32', xmerl_ucs:from_ucs4le(C)};
+unicode_decode([254,255|C]) -> {'utf-16', xmerl_ucs:from_utf16be(C)};
+unicode_decode([239,187,191|C]) -> {'utf-8', xmerl_ucs:from_utf8(C)};
+unicode_decode(C=[0,0,_,_|_]) -> {'utf-32be', xmerl_ucs:from_ucs4be(C)};
+unicode_decode(C=[_,_,0,0|_]) -> {'utf-32le', xmerl_ucs:from_ucs4le(C)};
+unicode_decode(C=[0,_|_]) -> {'utf-16be', xmerl_ucs:from_utf16be(C)};
+unicode_decode(C=[_,0|_]) -> {'utf-16le', xmerl_ucs:from_utf16le(C)};
+unicode_decode(C=_) -> {'utf-8', xmerl_ucs:from_utf8(C)}.
+
+%% @spec (EncodingAndCharacters::{Encoding, [char()]}) -> [byte()]
+%% where Encoding = 'utf-32' | 'utf-32be' | 'utf-32le' | 'utf-16' |
+%% 'utf-16be' | 'utf-16le' | 'utf-8'
+%%
+%% @doc Encodes the given characters to bytes, using the given Unicode encoding.
+%%
+%% For convenience, we supply a partial inverse of unicode_decode; If
+%% a BOM is requested, we more-or-less arbitrarily pick the big-endian
+%% variant of the encoding, since big-endian is network-order. We
+%% don't support UTF-8 with BOM here.
+unicode_encode({'utf-32', C}) -> [0,0,254,255|xmerl_ucs:to_ucs4be(C)];
+unicode_encode({'utf-32be', C}) -> xmerl_ucs:to_ucs4be(C);
+unicode_encode({'utf-32le', C}) -> xmerl_ucs:to_ucs4le(C);
+unicode_encode({'utf-16', C}) -> [254,255|xmerl_ucs:to_utf16be(C)];
+unicode_encode({'utf-16be', C}) -> xmerl_ucs:to_utf16be(C);
+unicode_encode({'utf-16le', C}) -> xmerl_ucs:to_utf16le(C);
+unicode_encode({'utf-8', C}) -> xmerl_ucs:to_utf8(C).
+
+parse([$" | Rest]) -> %% " emacs balancing
+ {Codepoints, Rest1} = parse_string(Rest, []),
+ {list_to_binary(xmerl_ucs:to_utf8(Codepoints)), Rest1};
+parse("true" ++ Rest) -> {true, Rest};
+parse("false" ++ Rest) -> {false, Rest};
+parse("null" ++ Rest) -> {null, Rest};
+parse([${ | Rest]) -> parse_object(skipws(Rest), []);
+parse([$[ | Rest]) -> parse_array(skipws(Rest), []);
+parse([]) -> exit(unexpected_end_of_input);
+parse(Chars) -> parse_number(Chars, []).
+
+skipws([X | Rest]) when X =< 32 ->
+ skipws(Rest);
+skipws(Chars) ->
+ Chars.
+
+parse_string(Chars, Acc) ->
+ case parse_codepoint(Chars) of
+ {done, Rest} ->
+ {lists:reverse(Acc), Rest};
+ {ok, Codepoint, Rest} ->
+ parse_string(Rest, [Codepoint | Acc])
+ end.
+
+parse_codepoint([$" | Rest]) -> %% " emacs balancing
+ {done, Rest};
+parse_codepoint([$\\, Key | Rest]) ->
+ parse_general_char(Key, Rest);
+parse_codepoint([X | Rest]) ->
+ {ok, X, Rest}.
+
+parse_general_char($b, Rest) -> {ok, 8, Rest};
+parse_general_char($t, Rest) -> {ok, 9, Rest};
+parse_general_char($n, Rest) -> {ok, 10, Rest};
+parse_general_char($f, Rest) -> {ok, 12, Rest};
+parse_general_char($r, Rest) -> {ok, 13, Rest};
+parse_general_char($/, Rest) -> {ok, $/, Rest};
+parse_general_char($\\, Rest) -> {ok, $\\, Rest};
+parse_general_char($", Rest) -> {ok, $", Rest};
+parse_general_char($u, [D0, D1, D2, D3 | Rest]) ->
+ Codepoint =
+ (digit_hex(D0) bsl 12) +
+ (digit_hex(D1) bsl 8) +
+ (digit_hex(D2) bsl 4) +
+ (digit_hex(D3)),
+ if
+ Codepoint >= 16#D800 andalso Codepoint < 16#DC00 ->
+ % High half of surrogate pair
+ case parse_codepoint(Rest) of
+ {low_surrogate_pair, Codepoint2, Rest1} ->
+ [FinalCodepoint] =
+ xmerl_ucs:from_utf16be(<<Codepoint:16/big-unsigned-integer,
+ Codepoint2:16/big-unsigned-integer>>),
+ {ok, FinalCodepoint, Rest1};
+ _ ->
+ exit(incorrect_usage_of_surrogate_pair)
+ end;
+ Codepoint >= 16#DC00 andalso Codepoint < 16#E000 ->
+ {low_surrogate_pair, Codepoint, Rest};
+ true ->
+ {ok, Codepoint, Rest}
+ end.
+
+%% @spec (Hexchar::char()) -> integer()
+%% @doc Returns the number corresponding to Hexchar.
+%%
+%% Hexchar must be one of the characters `$0' through `$9', `$A'
+%% through `$F' or `$a' through `$f'.
+digit_hex($0) -> 0;
+digit_hex($1) -> 1;
+digit_hex($2) -> 2;
+digit_hex($3) -> 3;
+digit_hex($4) -> 4;
+digit_hex($5) -> 5;
+digit_hex($6) -> 6;
+digit_hex($7) -> 7;
+digit_hex($8) -> 8;
+digit_hex($9) -> 9;
+
+digit_hex($A) -> 10;
+digit_hex($B) -> 11;
+digit_hex($C) -> 12;
+digit_hex($D) -> 13;
+digit_hex($E) -> 14;
+digit_hex($F) -> 15;
+
+digit_hex($a) -> 10;
+digit_hex($b) -> 11;
+digit_hex($c) -> 12;
+digit_hex($d) -> 13;
+digit_hex($e) -> 14;
+digit_hex($f) -> 15.
+
+finish_number(Acc, Rest) ->
+ Str = lists:reverse(Acc),
+ {case catch list_to_integer(Str) of
+ {'EXIT', _} -> list_to_float(Str);
+ Value -> Value
+ end, Rest}.
+
+parse_number([$- | Rest], Acc) ->
+ parse_number1(Rest, [$- | Acc]);
+parse_number(Rest = [C | _], Acc) ->
+ case is_digit(C) of
+ true -> parse_number1(Rest, Acc);
+ false -> exit(syntax_error)
+ end.
+
+parse_number1(Rest, Acc) ->
+ {Acc1, Rest1} = parse_int_part(Rest, Acc),
+ case Rest1 of
+ [] -> finish_number(Acc1, []);
+ [$. | More] ->
+ {Acc2, Rest2} = parse_int_part(More, [$. | Acc1]),
+ parse_exp(Rest2, Acc2, false);
+ _ ->
+ parse_exp(Rest1, Acc1, true)
+ end.
+
+parse_int_part(Chars = [_Ch | _Rest], Acc) ->
+ parse_int_part0(Chars, Acc).
+
+parse_int_part0([], Acc) ->
+ {Acc, []};
+parse_int_part0([Ch | Rest], Acc) ->
+ case is_digit(Ch) of
+ true -> parse_int_part0(Rest, [Ch | Acc]);
+ false -> {Acc, [Ch | Rest]}
+ end.
+
+parse_exp([$e | Rest], Acc, NeedFrac) ->
+ parse_exp1(Rest, Acc, NeedFrac);
+parse_exp([$E | Rest], Acc, NeedFrac) ->
+ parse_exp1(Rest, Acc, NeedFrac);
+parse_exp(Rest, Acc, _NeedFrac) ->
+ finish_number(Acc, Rest).
+
+parse_exp1(Rest, Acc, NeedFrac) ->
+ {Acc1, Rest1} = parse_signed_int_part(Rest, if
+ NeedFrac -> [$e, $0, $. | Acc];
+ true -> [$e | Acc]
+ end),
+ finish_number(Acc1, Rest1).
+
+parse_signed_int_part([$+ | Rest], Acc) ->
+ parse_int_part(Rest, [$+ | Acc]);
+parse_signed_int_part([$- | Rest], Acc) ->
+ parse_int_part(Rest, [$- | Acc]);
+parse_signed_int_part(Rest, Acc) ->
+ parse_int_part(Rest, Acc).
+
+is_digit($0) -> true;
+is_digit($1) -> true;
+is_digit($2) -> true;
+is_digit($3) -> true;
+is_digit($4) -> true;
+is_digit($5) -> true;
+is_digit($6) -> true;
+is_digit($7) -> true;
+is_digit($8) -> true;
+is_digit($9) -> true;
+is_digit(_) -> false.
+
+parse_object([$} | Rest], Acc) ->
+ {{obj, lists:reverse(Acc)}, Rest};
+parse_object([$, | Rest], Acc) ->
+ parse_object(skipws(Rest), Acc);
+parse_object([$" | Rest], Acc) -> %% " emacs balancing
+ {KeyCodepoints, Rest1} = parse_string(Rest, []),
+ [$: | Rest2] = skipws(Rest1),
+ {Value, Rest3} = parse(skipws(Rest2)),
+ parse_object(skipws(Rest3), [{KeyCodepoints, Value} | Acc]).
+
+parse_array([$] | Rest], Acc) ->
+ {lists:reverse(Acc), Rest};
+parse_array([$, | Rest], Acc) ->
+ parse_array(skipws(Rest), Acc);
+parse_array(Chars, Acc) ->
+ {Value, Rest} = parse(Chars),
+ parse_array(skipws(Rest), [Value | Acc]).
+
+%% @spec (Record, atom(), [any()]) -> jsonobj()
+%% where Record = tuple()
+%%
+%% @doc Used by the `?RFC4627_FROM_RECORD' macro in `rfc4627.hrl'.
+%%
+%% Given a record type definiton of ``-record(myrecord, {field1,
+%% field})'', and a value ``V = #myrecord{}'', the code
+%% ``?RFC4627_FROM_RECORD(myrecord, V)'' will return a JSON "object"
+%% with fields corresponding to the fields of the record. The macro
+%% expands to a call to the `from_record' function.
+from_record(R, _RecordName, Fields) ->
+ {obj, encode_record_fields(R, 2, Fields)}.
+
+encode_record_fields(_R, _Index, []) ->
+ [];
+encode_record_fields(R, Index, [Field | Rest]) ->
+ case element(Index, R) of
+ undefined ->
+ encode_record_fields(R, Index + 1, Rest);
+ Value ->
+ [{atom_to_list(Field), Value} | encode_record_fields(R, Index + 1, Rest)]
+ end.
+
+%% @spec (JsonObject::jsonobj(), DefaultValue::Record, [atom()]) -> Record
+%% where Record = tuple()
+%%
+%% @doc Used by the `?RFC4627_TO_RECORD' macro in `rfc4627.hrl'.
+%%
+%% Given a record type definiton of ``-record(myrecord, {field1,
+%% field})'', and a JSON "object" ``J = {obj, [{"field1", 123},
+%% {"field2", 234}]}'', the code ``?RFC4627_TO_RECORD(myrecord, J)''
+%% will return a record ``#myrecord{field1 = 123, field2 = 234}''.
+%% The macro expands to a call to the `to_record' function.
+to_record({obj, Values}, Fallback, Fields) ->
+ list_to_tuple([element(1, Fallback) | decode_record_fields(Values, Fallback, 2, Fields)]).
+
+decode_record_fields(_Values, _Fallback, _Index, []) ->
+ [];
+decode_record_fields(Values, Fallback, Index, [Field | Rest]) ->
+ [case lists:keysearch(atom_to_list(Field), 1, Values) of
+ {value, {_, Value}} ->
+ Value;
+ false ->
+ element(Index, Fallback)
+ end | decode_record_fields(Values, Fallback, Index + 1, Rest)].
+
+%% @spec (JsonObject::jsonobj(), atom()) -> {ok, json()} | not_found
+%% @doc Retrieves the value of a named field of a JSON "object".
+get_field({obj, Props}, Key) ->
+ case lists:keysearch(Key, 1, Props) of
+ {value, {_K, Val}} ->
+ {ok, Val};
+ false ->
+ not_found
+ end.
+
+%% @spec (jsonobj(), atom(), json()) -> json()
+%% @doc Retrieves the value of a named field of a JSON "object", or a
+%% default value if no such field is present.
+get_field(Obj, Key, DefaultValue) ->
+ case get_field(Obj, Key) of
+ {ok, Val} ->
+ Val;
+ not_found ->
+ DefaultValue
+ end.
+
+%% @spec (JsonObject::jsonobj(), atom(), json()) -> jsonobj()
+%% @doc Adds or replaces a named field with the given value.
+%%
+%% Returns a JSON "object" that contains the new field value as well
+%% as all the unmodified fields from the first argument.
+set_field({obj, Props}, Key, NewValue) ->
+ {obj, [{Key, NewValue} | lists:keydelete(Key, 1, Props)]}.
+
+%% @spec (A::json(), B::json()) -> bool()
+%% @doc Tests equivalence of JSON terms.
+%%
+%% After Bob Ippolito's `equiv' predicate in mochijson.
+equiv({obj, Props1}, {obj, Props2}) ->
+ L1 = lists:keysort(1, Props1),
+ L2 = lists:keysort(1, Props2),
+ equiv_sorted_plists(L1, L2);
+equiv(A, B) when is_list(A) andalso is_list(B) ->
+ equiv_arrays(A, B);
+equiv(A, B) ->
+ A == B.
+
+equiv_sorted_plists([], []) -> true;
+equiv_sorted_plists([], _) -> false;
+equiv_sorted_plists(_, []) -> false;
+equiv_sorted_plists([{K1, V1} | R1], [{K2, V2} | R2]) ->
+ K1 == K2 andalso equiv(V1, V2) andalso equiv_sorted_plists(R1, R2).
+
+equiv_arrays([], []) -> true;
+equiv_arrays([], _) -> false;
+equiv_arrays(_, []) -> false;
+equiv_arrays([V1 | R1], [V2 | R2]) ->
+ equiv(V1, V2) andalso equiv_arrays(R1, R2).
View
554 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc.erl
@@ -0,0 +1,554 @@
+%% JSON-RPC, transport-neutral.
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%% @since 1.2.0
+%%
+%% @reference the <a href="http://json-rpc.org/wd/JSON-RPC-1-1-WD-20060807.html">JSON-RPC specification</a> (draft; <a href="JSON-RPC-1-1-WD-20060807.html">mirrored locally</a>)
+%%
+%% @doc Provides a registry of running JSON-RPC objects, and a
+%% transport-neutral means of invoking methods defined on such
+%% objects.
+%%
+%% Other modules provide interfaces to specific transports and
+%% transport implementations. See {@link rfc4627_jsonrpc_http} and
+%% {@link rfc4627_jsonrpc_inets}, for example.
+%%
+%% == JSON-RPC Objects are Erlang Processes ==
+%%
+%% In the normal case, each JSON-RPC object in a running system
+%% corresponds to one Erlang process. This makes object lifecycle
+%% control very natural.
+%%
+%% == Basic usage ==
+%%
+%% Ensure the registry process is running, using {@link start/0} or
+%% {@link start_link/0}. Once it's up and running, use {@link
+%% register_service/2} to expose a process as a JSON-RPC service.
+%%
+%% To register a service, you will need to describe the methods
+%% available on it. Use {@link service/4} to do so.
+%%
+%% == Implementing a service ==
+%%
+%% Your service should be implemented by a `gen_server'
+%% process. JSON-RPC requests will be sent to it as
+%% `gen_server:call/2' messages:
+%%
+%% ``{jsonrpc, ProcedureNameBin, RequestInfo, Args}''
+%%
+%% Your module's `handle_call' function should respond to these
+%% messages with a reply of type {@type jsonrpc_response()}.
+%%
+%% Here's the implementation of the "test_proc" example:
+%%
+%% ```
+%% handle_call({jsonrpc, <<"test_proc">>, _RequestInfo, [Value]}, _From, State) ->
+%% {reply, {result, <<"ErlangServer: ", Value/binary>>}, State}.
+%% '''
+%%
+%% See also the complete example Erlang module included with the
+%% source code, `test_jsonrpc_inets.erl'.
+%%
+%% == Registering a service with the Service Registry ==
+%%
+%% You will need to ``-include("rfc4627_jsonrpc.hrl").'' (Or, if
+%% you've installed the compiled `rfc4627_jsonrpc' code in your Erlang
+%% lib directory,
+%% ``-include_lib("rfc4627/include/rfc4627_jsonrpc.hrl").'')
+%%
+%% The service registry must be started before any registrations can
+%% be performed: simply call {@link start/0} or {@link
+%% start_link/0}. This will start the registry if it wasn't running,
+%% or if it was, it will inform you of the existing registry's Pid.
+%%
+%% Registering a service is as simple as starting a process to receive
+%% service requests, and passing its pid to `rfc4627_jsonrpc' along
+%% with a <a
+%% href="JSON-RPC-1-1-WD-20060807.html#ServiceDescription">service
+%% descriptor</a> object built from Erlang records defined in
+%% `mod_jsonrpc.hrl':
+%%
+%% ```
+%% {ok, Pid} = gen_server:start(?MODULE, [], []),
+%% rfc4627_jsonrpc:register_service
+%% (Pid,
+%% rfc4627_jsonrpc:service(<<"test">>,
+%% <<"urn:uuid:afe1b4b5-23b0-4964-a74a-9168535c96b2">>,
+%% <<"1.0">>,
+%% [#service_proc{name = <<"test_proc">>,
+%% idempotent = true,
+%% params = [#service_proc_param{name = <<"value">>,
+%% type = <<"str">>}]}])).
+%% '''
+%%
+%% This code registers a service called "test":
+%%
+%% <ul>
+%% <li>its name is "test"</li>
+%% <li>its identifier (JSON-RPC's service description "id" field) is "urn:uuid:afe1b4b5-23b0-4964-a74a-9168535c96b2"</li>
+%% <li>its version string is "1.0"</li>
+%% <li>
+%% it defines just one method/procedure, which
+%% <ul>
+%% <li>is named "test_proc"</li>
+%% <li>is marked "idempotent", which means it is permitted to be accessed via HTTP GET instead of only HTTP POST</li>
+%% <li>has a single parameter named "value" of type "str"</li>
+%% </ul>
+%% </li>
+%% </ul>
+%%
+%% Note that almost all of the string values are expressed as
+%% binaries: this is because {@link rfc4627} uses binaries to
+%% represent JSON strings.
+%%
+%% To register a service with multiple procedures, add additional
+%% `#service_proc' records to the procedure list in the call to {@link
+%% service/4}. Similarly, additional parameters for each procedure can
+%% be defined by the addition of extra `#service_proc_param' records
+%% in the appropriate place.
+%%
+%% The available types for parameters are the strings defined in <a
+%% href="JSON-RPC-1-1-WD-20060807.html#ParameterReturnTypeStrings">this
+%% part</a> of the JSON-RPC specification, namely "bit", "num", "str",
+%% "arr", "obj", "any" or "nil". See also
+%% `rfc4627_jsonrpc:proc_param_type/1'.
+%%
+%% == Invoking methods on local services ==
+%%
+%% Usually, JSON-RPC services are invoked via HTTP using {@link
+%% rfc4627_jsonrpc_inets} or similar. However, the interface used by
+%% specific network transports to call methods on services is also
+%% available to ordinary programs. (And, of course, programs that
+%% implement new kinds of network transport for JSON-RPC.)
+%%
+%% To invoke a local service method, first retrieve its service
+%% descriptor using {@link lookup_service/1}. Then use {@link
+%% jsonrpc_post/3} or {@link invoke_service_method/8} to call a method
+%% on the service.
+%%
+%% The service record as retrieved from the registry contains the pid
+%% of the process responsible for handling service requests.
+%%
+%% == Experimental extension: 'Stateless' services ==
+%%
+%% Instead of registering a pid with the `rfc4627_jsonrpc'
+%% registry, an alternative is to use a service record with a
+%% function object instead of a pid. This allows more control over
+%% how a service is implemented: if using a `gen_server'
+%% service is too heavy, a function object that sends a simple
+%% message could be used; or if the service didn't need an
+%% implementing process at all, the function object could process
+%% the request without sending any messages at all.
+%%
+%% At the moment, the `rfc4627_jsonrpc' service registry
+%% only allows registration of `gen_server'-based
+%% pid-style services; this restriction will be lifted in a future
+%% release.
+%%
+%% To build a service descriptor object with a function handler
+%% instead of a pid, call `rfc4627_jsonrpc:service/5'
+%% instead of `rfc4627_jsonrpc:service/4':
+%%
+%% ```
+%% rfc4627_jsonrpc:service({function, fun my_handler/3}, Name, Id, Version, Procs)
+%% -> service descriptor object
+%%
+%% my_handler(ProcedureNameBin, RequestInfo, Args) -> jsonrpc_response()
+%% '''
+%%
+%% The resulting service descriptor can be used directly with {@link
+%% invoke_service_method/8}.
+%%
+%% @type service() = #service{}. A service description record, as
+%% defined in `rfc4627_jsonrpc.hrl'. Can be constructed using {@link
+%% service/4}, or retrieved from the registry using {@link
+%% lookup_service/1}.
+%%
+%% @type json() = rfc4627:json(). A JSON value.
+%% @type jsonobj() = rfc4627:jsonobj(). A JSON "object" or "struct".
+%% @type jsonarray() = rfc4627:jsonarray(). A JSON array.
+%%
+%% @type jsonrpc_response() = {(result | error), json()} | {(result | error), json(), jsonobj()}.
+%%
+%% The type that JSON-RPC service implementations are required to
+%% return.
+%%
+%% The first value should be `result' for a normal return value, or
+%% `error' for an error response. Use {@link error_response/2} or
+%% {@link error_response/3} to construct approprate error response
+%% values.
+%%
+%% The second value is the main response body: for normal returns,
+%% this is the ordinary return value of the procedure, and for error
+%% responses, it is the response structure defined in the JSON-RPC
+%% specification.
+%%
+%% The third, optional, value is called `ResponseInfo'. It can be used
+%% by the service implementation to supply transport-specific
+%% information back to its caller. For instance, if invoked via HTTP,
+%% extra headers to send back to the HTTP client can be passed in the
+%% `ResponseInfo' object. If `ResponseInfo' is omitted, `{obj, []}' is
+%% assumed.
+
+-module(rfc4627_jsonrpc).
+-include("rfc4627.hrl").
+-include("rfc4627_jsonrpc.hrl").
+
+-export([start/0, start_link/0]).
+
+-export([lookup_service/1, register_service/2]).
+-export([gen_object_name/0, system_describe/2]).
+-export([jsonrpc_post/3, jsonrpc_post/4, invoke_service_method/8, expand_jsonrpc_reply/2]).
+-export([error_response/2, error_response/3, service/4, service/5, proc/2]).
+
+-define(SERVICE, ?MODULE).
+
+%% @spec () -> {ok, pid()} | {error, {already_started, pid()}}
+%% @doc Starts the registry process.
+start() ->
+ gen_server:start({local, ?SERVICE}, rfc4627_jsonrpc_registry, [], []).
+
+%% @spec () -> {ok, pid()} | {error, {already_started, pid()}}
+%% @doc Starts the registry process, linking it to the calling process.
+start_link() ->
+ gen_server:start_link({local, ?SERVICE}, rfc4627_jsonrpc_registry, [], []).
+
+%% @spec (binary()) -> not_found | service()
+%% @doc Calls the registry to look up a service by name.
+lookup_service(Service) ->
+ gen_server:call(?SERVICE, {lookup_service, Service}).
+
+%% @spec (pid(), service()) -> ok
+%% @doc Registers a JSON-RPC service.
+%%
+%% The name of the service is contained within its service record.
+register_service(Pid, ServiceDescription) ->
+ %%error_logger:info_msg("Registering ~p as ~p", [Pid, ServiceDescription]),
+ gen_server:call(?SERVICE, {register_service, Pid, ServiceDescription}).
+
+%% @spec () -> string()
+%% @doc Generates a unique name that can be used for otherwise unnamed JSON-RPC services.
+gen_object_name() ->
+ Hash = erlang:md5(term_to_binary({node(), erlang:now()})),
+ binary_to_hex(Hash).
+
+%% @spec (binary(), Service::service()) -> jsonobj()
+%% @doc Builds a JSON-RPC service description JSON object.
+%%
+%% This is used in the implementation of the `system.describe'
+%% JSON-RPC method that all JSON-RPC services are required by the
+%% specification to support.
+%%
+%% If `EndpointAddress' is `undefined', no `address' field is returned
+%% in the resulting description. Otherwise, it is included
+%% verbatim. The other fields in the description are constructed using
+%% the information in the `Service' record.
+system_describe(EndpointAddress,
+ #service{name = Name, id = Id, version = Version, summary = Summary,
+ help = Help, procs = Procs}) ->
+ remove_undefined({obj, [{"sdversion", <<"1.0">>},
+ {"name", Name},
+ {"id", Id},
+ {"version", Version},
+ {"summary", Summary},
+ {"help", Help},
+ {"address", EndpointAddress},
+ {"procs", [system_describe_proc(P) || P <- Procs]}]}).
+
+%% @spec (service(), jsonobj(), jsonobj()) -> jsonrpc_response()
+%% @doc Calls {@link jsonrpc_post/4} with a `Timeout' of `default'.
+jsonrpc_post(ServiceRec, RequestInfo, RequestObj) ->
+ jsonrpc_post(ServiceRec, RequestInfo, RequestObj, default).
+
+%% @spec (service(), jsonobj(), jsonobj(), Timeout) -> jsonrpc_response()
+%% where Timeout = default | infinity | integer()
+%%
+%% @doc Performs a POST-style invocation of a JSON-RPC service method.
+%%
+%% `RequestObj' is to be a JSON "object" containing at minimum fields
+%% named "id", "method" and "params", with meanings as defined by the
+%% JSON-RPC specification.
+%%
+%% See {@link invoke_service_method/8} for descriptions of the other
+%% parameters.
+jsonrpc_post(ServiceRec, RequestInfo, RequestObj, Timeout) ->
+ Id = rfc4627:get_field(RequestObj, "id", undefined),
+ Method = rfc4627:get_field(RequestObj, "method", undefined),
+ Args = rfc4627:get_field(RequestObj, "params", undefined),
+ invoke_service_method(ServiceRec, Id, post, RequestInfo, undefined, Method, Args, Timeout).
+
+%% @spec (ServiceRec, RequestId, PostOrGet, RequestInfo, EndpointAddress, Method, Args, Timeout)
+%% -> jsonrpc_response()
+%% where ServiceRec = service()
+%% RequestId = integer() | null
+%% PostOrGet = post | get
+%% RequestInfo = jsonobj()
+%% EndpointAddress = binary() | undefined
+%% Method = binary()
+%% Args = jsonobj() | jsonarray()
+%% Timeout = default | infinity | integer()
+%%
+%% @doc Calls a method defined on a JSON-RPC service.
+%%
+%% Use {@link lookup_service/1} or {@link service/5} to get a usable
+%% #service record for use with this function.
+%%
+%% The request ID should be the ID from the JSON-RPC request, as it
+%% was encoded for the transport the request arrived on. It will be
+%% used by this function in constructing the JSON-RPC reply
+%% object. Since the request ID is optional, it is acceptable to
+%% supply `null' instead of an integer.
+%%
+%% The `PostOrGet' parameter is used to check the idempotency setting
+%% for the chosen service procedure. If the parameter is passed as
+%% `post', no check is performed, as it is assumed that a stateful
+%% method call is permitted; if it is passed as `get', then the
+%% idempotency flag is checked, and an error object may be returned in
+%% the case that the invoked method is non-idempotent.
+%%
+%% The `RequestInfo' structure contains transport-specific details
+%% about the request. For HTTP, for example, this will include the
+%% HTTP headers and HTTP method. For AMQP, it will include the
+%% exchange and routing-key.
+%%
+%% The `EndpointAddress' is only used in the case that the method
+%% being invoked is `system.describe', in which case it is
+%% incorporated into the returned description as detailed in the
+%% documentation for {@link system_describe/2}.
+%%
+%% `Method' is the name of the service method to invoke, and `Args' is
+%% either a JSON "object" or a JSON array, to serve as the parameters
+%% for the call.
+%%
+%% The `Timeout' parameter is used to control how long the system will
+%% wait for a reply from the backing gen_server. If `default' is
+%% specified, the default `gen_server:call' timeout is used;
+%% otherwise, `infinity' or a number of milliseconds is passed in to
+%% the `gen_server:call'.
+invoke_service_method(ServiceRec = #service{}, RequestId,
+ PostOrGet, RequestInfo, EndpointAddress, Method, Args, Timeout) ->
+ expand_jsonrpc_reply(
+ RequestId,
+ case Method of
+ <<"system.describe">> ->
+ {result, system_describe(EndpointAddress, ServiceRec)};
+ <<"system.", _Rest/binary>> ->
+ error_response(403, "System methods forbidden", Method);
+ _ ->
+ case lookup_service_proc(ServiceRec, Method) of
+ {ok, ServiceProc} ->
+ invoke_service(PostOrGet, ServiceRec#service.handler,
+ RequestInfo, ServiceProc, Args, Timeout);
+ not_found ->
+ error_response(404, "Procedure not found", [EndpointAddress, Method])
+ end
+ end).
+
+%% @spec (CodeOrMessage::(integer() | string() | binary()), json()) -> {error, jsonobj()}
+%% @doc Constructs an error response as per the JSON-RPC specification.
+%%
+%% Either a code or a message can be supplied as the first argument.
+%%
+%% @see error_response/3
+error_response(Code, ErrorValue) when is_integer(Code) ->
+ error_response(Code, "Error "++integer_to_list(Code), ErrorValue);
+error_response(Message, ErrorValue) when is_list(Message) ->
+ error_response(500, list_to_binary(Message), ErrorValue);
+error_response(Message, ErrorValue) when is_binary(Message) ->
+ error_response(500, Message, ErrorValue).
+
+%% @spec (integer(), (string() | binary()), json()) -> {error, jsonobj()}
+%% @doc Constructs an error response as per the JSON-RPC specification.
+%%
+%% The first argument should hold the error code. Error codes are
+%% defined in the JSON-RPC specification.
+%%
+%% The second argument can be either a `string()' or a `binary()'
+%% describing the error as text.
+%%
+%% The third argument is a general JSON value providing arbitrary
+%% further detail on the error.
+error_response(Code, Message, ErrorValue) when is_list(Message) ->
+ error_response(Code, list_to_binary(Message), ErrorValue);
+error_response(Code, Message, ErrorValue) ->
+ {error, {obj, [{"name", <<"JSONRPCError">>},
+ {"code", Code},
+ {"message", Message},
+ {"error", ErrorValue}]}}.
+
+%% @spec (Name, Id, Version, Procs) -> service()
+%% where Name = binary() | string()
+%% Id = binary() | string()
+%% Version = binary() | string()
+%% Procs = [ProcedureDescription]
+%% ProcedureDescription = {Name, [Parameter]} | #service_proc{}
+%% Parameter = {Name, ParameterType} | #service_proc_param{}
+%% ParameterType = bit | num | str | arr | obj | any | nil | string() | binary()
+%%
+%% @doc Constructs a service description record.
+%%
+%% The `Procs' parameter should be a list of procedure-descriptions,
+%% which can be either constructed manually or using {@link proc/2}.
+service(Name, Id, Version, Procs) when is_list(Name) ->
+ service(list_to_binary(Name), Id, Version, Procs);
+service(Name, Id, Version, Procs) when is_list(Id) ->
+ service(Name, list_to_binary(Id), Version, Procs);
+service(Name, Id, Version, Procs) when is_list(Version) ->
+ service(Name, Id, list_to_binary(Version), Procs);
+service(Name, Id, Version, Procs) ->
+ #service{name = Name, id = Id, version = Version,
+ procs = [case P of
+ {ProcName, Params} -> proc(ProcName, Params);
+ #service_proc{} -> P
+ end || P <- Procs]}.
+
+%% @spec (Handler, Name, Id, Version, Procs) -> service()
+%% where Handler = {pid, pid()} | {function, function()}
+%%
+%% @doc As for {@link service/4}, but supplying a handler for use with
+%% an experimental "stateless" service implementation.
+service(Handler, Name, Id, Version, Procs) ->
+ (service(Name, Id, Version, Procs))#service{handler = Handler}.
+
+%% @spec (Name, [Parameter]) -> #service_proc{}
+%% where Name = binary() | string()
+%% Parameter = {Name, ParameterType} | #service_proc_param{}
+%% ParameterType = bit | num | str | arr | obj | any | nil | string() | binary()
+%%
+%% @doc Constructs a service procedure description record.
+proc(Name, Params) when is_list(Name) ->
+ proc(list_to_binary(Name), Params);
+proc(Name, Params) ->
+ #service_proc{name = Name, params = [proc_param(P) || P <- Params]}.
+
+%---------------------------------------------------------------------------
+
+build_jsonrpc_response(Id, ResultField) ->
+ {obj, [{version, <<"1.1">>},
+ {id, Id},
+ ResultField]}.
+
+expand_jsonrpc_reply(RequestId, {ResultOrError, Value}) ->
+ {ResultOrError, build_jsonrpc_response(RequestId, {ResultOrError, Value}), {obj, []}};
+expand_jsonrpc_reply(RequestId, {ResultOrError, Value, ResponseInfo}) ->
+ {ResultOrError, build_jsonrpc_response(RequestId, {ResultOrError, Value}), ResponseInfo}.
+
+proc_param({N, T}) when is_list(N) ->
+ proc_param({list_to_binary(N), T});
+proc_param({N, T}) ->
+ #service_proc_param{name = N, type = proc_param_type(T)}.
+
+proc_param_type(bit) -> <<"bit">>;
+proc_param_type(num) -> <<"num">>;
+proc_param_type(str) -> <<"str">>;
+proc_param_type(arr) -> <<"arr">>;
+proc_param_type(obj) -> <<"obj">>;
+proc_param_type(any) -> <<"any">>;
+proc_param_type(nil) -> <<"nil">>;
+proc_param_type(T) when is_list(T) -> list_to_binary(T);
+proc_param_type(T) when is_binary(T) -> T.
+
+binary_to_hex(<<>>) ->
+ [];
+binary_to_hex(<<B, Rest/binary>>) ->
+ [rfc4627:hex_digit((B bsr 4) band 15),
+ rfc4627:hex_digit(B band 15) |
+ binary_to_hex(Rest)].
+
+lookup_service_proc(#service{procs = Procs}, Method) ->
+ case lists:keysearch(Method, #service_proc.name, Procs) of
+ {value, ServiceProc} ->
+ {ok, ServiceProc};
+ false ->
+ not_found
+ end.
+
+invoke_service(get, Handler, RequestInfo, ServiceProc, Args, Timeout) ->
+ if
+ ServiceProc#service_proc.idempotent ->
+ invoke_service1(Handler, RequestInfo, ServiceProc, Args, Timeout);
+ true ->
+ error_response(403, "Non-idempotent method", ServiceProc#service_proc.name)
+ end;
+invoke_service(post, Handler, RequestInfo, ServiceProc, Args, Timeout) ->
+ invoke_service1(Handler, RequestInfo, ServiceProc, Args, Timeout).
+
+invoke_service1(Handler, RequestInfo, #service_proc{name = Name, params = Params}, Args, Timeout) ->
+ %%error_logger:info_msg("JSONRPC invoking ~p:~p(~p)", [Handler, Name, Args]),
+ case catch run_handler(Handler, Name, RequestInfo, coerce_args(Params, Args), Timeout) of
+ {'EXIT', {{function_clause, _}, _}} ->
+ error_response(404, "Undefined procedure", Name);
+ {'EXIT', Reason} ->
+ error_response(500, "Internal error", list_to_binary(io_lib:format("~p", [Reason])));
+ Response ->
+ Response
+ end.
+
+run_handler({pid, Pid}, Name, RequestInfo, CoercedArgs, default) ->
+ gen_server:call(Pid, {jsonrpc, Name, RequestInfo, CoercedArgs});
+run_handler({pid, Pid}, Name, RequestInfo, CoercedArgs, Timeout) ->
+ gen_server:call(Pid, {jsonrpc, Name, RequestInfo, CoercedArgs}, Timeout);
+run_handler({function, F}, Name, RequestInfo, CoercedArgs, _Timeout) ->
+ F(Name, RequestInfo, CoercedArgs).
+
+coerce_args(_Params, Args) when is_list(Args) ->
+ Args;
+coerce_args(Params, {obj, Fields}) ->
+ [case lists:keysearch(binary_to_list(Name), 1, Fields) of
+ {value, {_, Value}} -> coerce_value(Value, Type);
+ false -> null
+ end || #service_proc_param{name = Name, type = Type} <- Params].
+
+coerce_value(Value, _Type) when not(is_binary(Value)) ->
+ Value;
+coerce_value(<<"true">>, <<"bit">>) -> true;
+coerce_value(_, <<"bit">>) -> false;
+coerce_value(V, <<"num">>) -> list_to_integer(binary_to_list(V));
+coerce_value(V, <<"str">>) -> V;
+coerce_value(V, <<"arr">>) -> rfc4627:decode(V);
+coerce_value(V, <<"obj">>) -> rfc4627:decode(V);
+coerce_value(V, <<"any">>) -> V;
+coerce_value(_, <<"nil">>) -> null;
+coerce_value(V, _) -> V.
+
+remove_undefined({obj, Fields}) ->
+ {obj, remove_undefined1(Fields)}.
+
+remove_undefined1([]) ->
+ [];
+remove_undefined1([{_, undefined} | Rest]) ->
+ remove_undefined1(Rest);
+remove_undefined1([X | Rest]) ->
+ [X | remove_undefined1(Rest)].
+
+system_describe_proc(P = #service_proc{params = Params}) ->
+ remove_undefined(?RFC4627_FROM_RECORD(service_proc,
+ P#service_proc{params = [system_describe_proc_param(A)
+ || A <- Params]})).
+
+system_describe_proc_param(P = #service_proc_param{}) ->
+ remove_undefined(?RFC4627_FROM_RECORD(service_proc_param, P)).
View
36 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_app.erl
@@ -0,0 +1,36 @@
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+
+-module(rfc4627_jsonrpc_app).
+
+-export([start/2, stop/1]).
+
+-behaviour(application).
+
+start(normal, []) ->
+ rfc4627_jsonrpc_sup:start_link().
+
+stop(_State) ->
+ ok.
View
172 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_http.erl
@@ -0,0 +1,172 @@
+%% JSON-RPC for HTTP transports (inets, mochiweb, yaws, ...)
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%% @since 1.2.0
+%%
+%% @doc General JSON-RPC HTTP transport support.
+%%
+%% Used by the Inets HTTP transport library, {@link
+%% rfc4627_jsonrpc_inets}.
+
+-module(rfc4627_jsonrpc_http).
+-include("rfc4627_jsonrpc.hrl").
+
+-export([invoke_service_method/4]).
+
+%% @spec (AliasPrefix, Path, RequestInfo, Body) -> Result
+%% where AliasPrefix = default | string()
+%% Path = string()
+%% RequestInfo = rfc4627:jsonobj()
+%% Body = string() | binary()
+%% Result = no_match | {ok, string(), jsonobj()}
+%%
+%% @doc Uses `AliasPrefix', `RequestInfo', `Path' and `Body' to locate
+%% and invoke a service procedure.
+%%
+%% If `AliasPrefix' is `default', the default prefix `"/jsonrpc"' is used.
+%%
+%% `Path' is expected to be the "path" part of the request URI: a string of the form "/.../prefix/objectname[/methodname]".
+%%
+%% `RequestInfo' is a JSON "object" containing HTTP-specific request information:
+%%
+%% <ul>
+%% <li>`http_method' should be either `<<"GET">>' (the default) or `<<"POST">>'</li>
+%% <li>`http_query_parameters' should be the query parameters (the part of the URL after "?")
+%% decoded into a JSON "object", with one field per parameter</li>
+%% <li>`http_headers' should be a JSON "object" containing the HTTP request headers</li>
+%% <li>`scheme' should be either `<<"http">>' or `<<"https">>'</li>
+%% <li>`remote_port' should be the TCP port number of the remote peer</li>
+%% <li>`remote_peername' should be a {@type binary()} containing the IP address of the
+%% remote peer, in a form acceptable to {@link inet:gethostbyaddr/1}</li>
+%% </ul>
+%%
+%% All the fields in `RequestInfo' are optional.
+%%
+%% `Body' must be either a {@type binary()} or a {@type string()}
+%% containing the HTTP request body.
+%%
+%% Operation proceeds as follows. `Path' is first matched against
+%% `AliasPrefix'. If it does not match, `no_match' is
+%% returned. Otherwise, the matching prefix is stripped from the path,
+%% and extraction of the service name, method name, and parameters from
+%% the HTTP request proceeds as per the JSON-RPC specification,
+%% [JSON-RPC-1-1-WD-20060807.html#ProcedureCall].
+%%
+%% Once the service name, method name and parameters are known, The
+%% service is looked up with {@link rfc4627_jsonrpc:lookup_service/1},
+%% and the named method is invoked with {@link
+%% rfc4627_jsonrpc:invoke_service_method/8}.
+%%
+%% The final result is encoded into a list of bytes using {@link
+%% rfc4627:encode/1}, and returned along with the `ResponseInfo' to
+%% our caller.
+invoke_service_method(default, Path, RequestInfo, Body) ->
+ invoke_service_method("/jsonrpc", Path, RequestInfo, Body);
+invoke_service_method(AliasPrefix, Path, RequestInfo, Body) ->
+ case parse_jsonrpc(AliasPrefix,
+ Path,
+ rfc4627:get_field(RequestInfo, "http_method", <<"GET">>),
+ rfc4627:get_field(RequestInfo, "http_query_parameters", {obj, []}),
+ Body) of
+ no_match ->
+ no_match;
+ {PostOrGet, Id, Service, Method, Args} ->
+ {_ResultOrError, Result, ResponseInfo} =
+ case rfc4627_jsonrpc:lookup_service(Service) of
+ not_found ->
+ Err = rfc4627_jsonrpc:error_response(404, "Service not found", Service),
+ rfc4627_jsonrpc:expand_jsonrpc_reply(Id, Err);
+ ServiceRec ->
+ HttpHeaders = rfc4627:get_field(RequestInfo, "http_headers", {obj, []}),
+ Timeout = extract_timeout_header(HttpHeaders),
+ EndpointAddress = service_address(AliasPrefix, RequestInfo, Service),
+ rfc4627_jsonrpc:invoke_service_method(
+ ServiceRec, Id,
+ PostOrGet, RequestInfo, EndpointAddress, Method, Args, Timeout)
+ end,
+ ResultEnc = lists:flatten(rfc4627:encode(Result)),
+ {ok, ResultEnc, ResponseInfo}
+ end.
+
+extract_timeout_header(HeadersJsonObj) ->
+ case rfc4627:get_field(HeadersJsonObj, "x-json-rpc-timeout", <<"default">>) of
+ <<"default">> ->
+ default;
+ <<"infinity">> ->
+ infinity;
+ Other ->
+ list_to_integer(binary_to_list(Other))
+ end.
+
+extract_object_and_method(AliasPrefix, Path) ->
+ AliasPrefixRe = "^" ++ AliasPrefix ++ "/",
+ case regexp:first_match(Path, AliasPrefixRe) of
+ {match, 1, Length} ->
+ ObjectMethod = string:substr(Path, Length + 1),
+ case lists:reverse(string:tokens(ObjectMethod, "/")) of
+ [] -> {<<>>, <<>>};
+ [Object] -> {list_to_binary(Object), <<>>};
+ [Method1, Object | _] -> {list_to_binary(Object), list_to_binary(Method1)}
+ end;
+ nomatch ->
+ no_match
+ end.
+
+parse_jsonrpc(AliasPrefix, Path, HttpMethod, QueryParametersObj, Body) ->
+ case extract_object_and_method(AliasPrefix, Path) of
+ no_match ->
+ no_match;
+ {Object, PathMethod} ->
+ case HttpMethod of
+ <<"POST">> ->
+ {ok, RequestObject, _} = rfc4627:decode(Body),
+ {post,
+ rfc4627:get_field(RequestObject, "id", null),
+ Object,
+ rfc4627:get_field(RequestObject, "method", undefined),
+ rfc4627:get_field(RequestObject, "params", undefined)};
+ _ ->
+ %% GET, presumably. We don't really care, here.
+ {get,
+ null,
+ Object,
+ PathMethod,
+ QueryParametersObj}
+ end
+ end.
+
+service_address(AliasPrefix, RequestInfo, ServiceName) ->
+ HttpHeaders = rfc4627:get_field(RequestInfo, "http_headers", {obj, []}),
+ Host = case rfc4627:get_field(HttpHeaders, "host", undefined) of
+ undefined -> "";
+ Name -> "//" ++ binary_to_list(Name)
+ end,
+ Scheme = case rfc4627:get_field(RequestInfo, "scheme", undefined) of
+ undefined -> "";
+ S -> binary_to_list(S) ++ ":"
+ end,
+ list_to_binary(Scheme ++ Host ++ AliasPrefix ++ "/" ++ binary_to_list(ServiceName)).
View
159 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_inets.erl
@@ -0,0 +1,159 @@
+%% JSON-RPC for Erlang's inets httpd
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%% @since 1.2.0
+%%
+%% @doc An extension module for the Inets HTTP server, providing HTTP access to JSON-RPC services.
+%%
+%% == Configuring HTTP access to registered JSON-RPC services ==
+%%
+%% The inets httpd uses an `httpd.conf' file to configure itself. To
+%% enable HTTP access to registered JSON-RPC services, two things need
+%% to be added to the httpd configuration file:
+%%
+%% <ul>
+%% <li>an entry for `rfc4627_jsonrpc_inets' in the `Modules' configuration directive (just after `mod_alias' and `mod_auth' will do)</li>
+%% <li>a `JsonRpcAlias' directive, specifying a subspace of the URLs served by the httpd that will be mapped to JSON-RPC service requests (the value used as `AliasPrefix' in calls to {@link rfc4627_jsonrpc_http:invoke_service_method/4})</li>
+%% </ul>
+%%
+%% Here's a complete `httpd.conf':
+%%
+%% ```
+%% ServerName localhost
+%% ServerRoot test/server_root
+%% DocumentRoot test/server_root/htdocs
+%% Port 5671
+%% Modules mod_alias mod_auth rfc4627_jsonrpc_inets mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
+%% DirectoryIndex index.html
+%% JsonRpcAlias /rpc
+%% ErrorLog logs/error_log
+%% TransferLog logs/access_log
+%% SecurityLog logs/security_log</pre>
+%% '''
+%%
+%% If an httpd server is started from this configuration, it will
+%%
+%% <ul>
+%% <li>listen on port 5671</li>
+%% <li>permit JSON-RPC access via URLs starting with `/rpc'</li>
+%% </ul>
+%%
+%% The URL structure for JSON-RPC requests will be
+%%
+%% <pre>http://localhost:5671/rpc/<i>ServiceName</i></pre>
+%%
+%% where ServiceName is the {@link
+%% rfc4627_jsonrpc:register_service/2}'d name of the service. For
+%% instance, the example "hello world" service defined in
+%% `test_jsonrpc_inets.erl' would be accessible at
+%%
+%% ``http://localhost:5671/rpc/test''
+%%
+%% The built-in service description method, `system.describe', is
+%% accessible via a POST to that URL, or a GET to
+%%
+%% ``http://localhost:5671/rpc/test/system.describe''
+%%
+%% Similarly, any idempotent methods provided by a service may be
+%% accessed via POST to the base URL for the service, or via GET to a
+%% URL of the form
+%%
+%% <pre>http://localhost:5671/rpc/<i>ServiceName</i>/<i>MethodName</i>?<i>arg</i>=<i>value</i>&amp;<i>...</i></pre>
+%%
+
+-module(rfc4627_jsonrpc_inets).
+-include("rfc4627_jsonrpc.hrl").
+-include_lib("inets/src/httpd.hrl").
+
+-export([do/1, load/2]).
+
+%% @spec (#mod{}) -> {proceed, term()}
+%% @doc Implements the inets httpd main callback interface.
+%%
+%% Calls out to {@link rfc4627_jsonrpc_http:invoke_service_method/4}.
+do(ModData = #mod{data = OldData}) ->
+ case {proplists:get_value(status, OldData),
+ proplists:get_value(response, OldData)} of
+ {undefined, undefined} ->
+ do_rpc(ModData);
+ _ ->
+ {proceed, OldData}
+ end.
+
+%% @spec (Line::string(), AccIn::term()) -> {ok, AccOut::term(), {atom(), term()}}
+%% @doc Parses the `"JsonRpcAlias"' configuration entry from the inets `httpd.conf' file.
+load("JsonRpcAlias " ++ Alias, []) ->
+ {ok, [], {json_rpc_alias, Alias}}.
+
+do_rpc(#mod{init_data = #init_data{peername = {PeerPort, PeerName}},
+ config_db = ConfigDb,
+ socket_type = SocketType,
+ method = HttpMethod,
+ request_uri = PathAndQuery,
+ parsed_header = InetsHeaders,
+ entity_body = Body,
+ data = OldData}) ->
+ AliasPrefix = httpd_util:lookup(ConfigDb, json_rpc_alias, default),
+ {Path, QueryObj} = case string:tokens(PathAndQuery, "?") of
+ [] -> {"", {obj, []}};
+ [P] -> {P, {obj, []}};
+ [P, Q] -> {P, {obj, case httpd:parse_query(Q) of
+ [{"",""}] -> []; %% is this a bug in httpd?
+ KV -> [{K, list_to_binary(V)} || {K,V} <- KV]
+ end}}
+ end,
+ HeaderObj = {obj, [{K, list_to_binary(V)} || {K,V} <- InetsHeaders]},
+ SchemeFields = case SocketType of
+ ip_comm -> [{"scheme", <<"http">>}];
+ ssl -> [{"scheme", <<"https">>}];
+ _ -> []
+ end,
+
+ RequestInfo = {obj, ([{"http_method", list_to_binary(HttpMethod)},
+ {"http_query_parameters", QueryObj},
+ {"http_headers", HeaderObj},
+ {"remote_port", PeerPort},
+ {"remote_peername", list_to_binary(PeerName)}]
+ ++ SchemeFields)},
+
+ case rfc4627_jsonrpc_http:invoke_service_method(AliasPrefix,
+ Path,
+ RequestInfo,
+ Body) of
+ no_match ->
+ {proceed, OldData};
+ {ok, ResultEnc, ResponseInfo} ->
+ {obj, ResponseHeaderFields} =
+ rfc4627:get_field(ResponseInfo, "http_headers", {obj, []}),
+ Headers = [{K, binary_to_list(V)} || {K,V} <- ResponseHeaderFields],
+ {proceed, [{response, {response,
+ [{code, 200},
+ {content_length, integer_to_list(length(ResultEnc))},
+ {content_type, "text/plain"}%rfc4627:mime_type()}
+ | Headers],
+ ResultEnc}} | OldData]}
+ end.
View
106 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_mochiweb.erl
@@ -0,0 +1,106 @@
+%% JSON-RPC for Mochiweb
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%% @since 1.2.0
+%%
+%% @reference the <a href="http://code.google.com/p/mochiweb/">Mochiweb home page</a>
+%%
+%% @doc Support for serving JSON-RPC via Mochiweb.
+%%
+%% Familiarity with writing Mochiweb applications is assumed.
+%%
+%% == Basic Usage ==
+%%
+%% <ul>
+%% <li>Register your JSON-RPC services as usual.</li>
+%% <li>Decide on your `AliasPrefix' (see {@link rfc4627_jsonrpc_http:invoke_service_method/4}).</li>
+%% <li>When a Mochiweb request arrives at your application, call {@link handle/2} with your `AliasPrefix' and the request.</li>
+%% </ul>
+%%
+%% It's as simple as that - if the request's URI path matches the
+%% `AliasPrefix', it will be decoded and the JSON-RPC service it names
+%% will be invoked.
+
+-module(rfc4627_jsonrpc_mochiweb).
+
+-export([handle/2]).
+
+normalize(X) when is_atom(X) ->
+ string:to_lower(atom_to_list(X));
+normalize(X) when is_binary(X) ->
+ string:to_lower(binary_to_list(X));
+normalize(X) when is_list(X) ->
+ string:to_lower(X).
+
+%% @spec (string(), #mochiweb_request{}) -> no_match | {ok, Response}
+%% where Response = {Code, ReplyHeaders, ReplyBody}
+%% Code = integer()
+%% ReplyHeaders = [{string(), string()}]
+%% ReplyBody = string()
+%%
+%% @doc If the request matches `AliasPrefix', the corresponding
+%% JSON-RPC service is invoked, and an `{ok, Response}' is returned;
+%% otherwise, `no_match' is returned.
+%%
+%% Call this function from your Mochiweb application's `loop'
+%% function, as follows:
+%%
+%% ```
+%% case rfc4627_jsonrpc_mochiweb:handle("/rpc", Req) of
+%% no_match ->
+%% handle_non_jsonrpc_request(Req);
+%% {ok, Response} ->
+%% Req:respond(Response)
+%% end
+%% '''
+%%
+%% where `handle_non_jsonrpc_request' does the obvious thing for
+%% non-JSON-RPC requests.
+handle(AliasPrefix, Req) ->
+ Path = Req:get(path),
+ QueryObj = {obj, [{K, list_to_binary(V)} || {K,V} <- Req:parse_qs()]},
+ HeaderObj = {obj, [{normalize(K), list_to_binary(V)}
+ || {K,V} <- mochiweb_headers:to_list(Req:get(headers))]},
+ RequestInfo = {obj, [{"http_method", list_to_binary(atom_to_list(Req:get(method)))},
+ {"http_query_parameters", QueryObj},
+ {"http_headers", HeaderObj},
+ {"remote_peername", list_to_binary(Req:get(peer))},
+ {"scheme", <<"http">>}]},
+ Body = Req:recv_body(),
+
+ case rfc4627_jsonrpc_http:invoke_service_method(AliasPrefix,
+ Path,
+ RequestInfo,
+ Body) of
+ no_match ->
+ no_match;
+ {ok, ResultEnc, ResponseInfo} ->
+ {obj, ResponseHeaderFields} =
+ rfc4627:get_field(ResponseInfo, "http_headers", {obj, []}),
+ Headers = [{K, binary_to_list(V)} || {K,V} <- ResponseHeaderFields],
+ {ok, {200, Headers ++ [{"Content-type", "text/plain"}], ResultEnc}}
+ end.
View
109 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_registry.erl
@@ -0,0 +1,109 @@
+%% JSON-RPC service registry
+%%---------------------------------------------------------------------------
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+%%---------------------------------------------------------------------------
+%% @since 1.2.0
+
+%% @private
+%% @doc JSON-RPC service registry implementation.
+%%
+%% Started and managed by functions defined in module {@link rfc4627_jsonrpc}.
+
+-module(rfc4627_jsonrpc_registry).
+-include("rfc4627_jsonrpc.hrl").
+
+-behaviour(gen_server).
+
+-export([init/1, terminate/2, code_change/3, handle_call/3, handle_cast/2, handle_info/2]).
+
+-define(TABLE_NAME, rfc4627_jsonrpc_registry).
+
+%% @doc gen_server behaviour callback.
+init(_Args) ->
+ case mnesia:create_table(?TABLE_NAME, [{attributes,[key, value]}]) of
+ {atomic,ok} ->
+ error_logger:error_msg("Creating new mnesia table for ~p~n", [?MODULE]),
+ ok;
+ {aborted, {already_exists, ?TABLE_NAME}} -> ok
+ end,
+ {ok, no_jsonrpc_state}.
+
+%% @doc gen_server behaviour callback.
+terminate(_Reason, _State) ->
+ %% FIXME: should we notify services here?
+ ok.
+
+%% @doc gen_server behaviour callback.
+code_change(_OldVsn, State, _Extra) ->
+ State.
+
+%% @doc gen_server behaviour callback.
+handle_call({lookup_service, Service}, _From, State) ->
+ case tx(lookup(service, Service)) of
+ none ->
+ {reply, not_found, State};
+ ServiceRec ->
+ {reply, ServiceRec, State}
+ end;
+
+handle_call({register_service, Pid, ServiceDescription}, _From, State) ->
+ SD = ServiceDescription#service{handler = {pid, Pid}},
+ erlang:monitor(process, Pid),
+ tx(fun() ->
+ mnesia:write({?TABLE_NAME,{service_pid, Pid}, SD#service.name}),
+ mnesia:write({?TABLE_NAME,{service, SD#service.name}, SD})
+ end),
+ {reply, ok, State}.
+
+%% @doc gen_server behaviour callback.
+handle_cast(Request, State) ->
+ error_logger:error_msg("Unhandled cast in ~p: ~p", [?MODULE, Request]),
+ {noreply, State}.
+
+%% @doc gen_server behaviour callback.
+handle_info({'DOWN', _MonitorRef, process, DownPid, _Reason}, State) ->
+ case tx(lookup(service_pid, DownPid)) of
+ none ->
+ {noreply, State};
+ ServiceName ->
+ tx(fun() ->
+ mnesia:delete({?TABLE_NAME,{service_pid, DownPid}}),
+ mnesia:delete({?TABLE_NAME,{service, ServiceName}})
+ end),
+ {noreply, State}
+ end.
+
+lookup(Type, Key) ->
+ fun() -> mnesia:read(?TABLE_NAME, {Type, Key}) end.
+
+tx(Fun) ->
+ case mnesia:transaction(Fun) of
+ {atomic, []} -> none;
+ {atomic, ok} -> ok;
+ {atomic, abort} -> abort;
+ {atomic, [{?TABLE_NAME, _, X}]} -> X
+ end.
+
View
42 contrib/erlang-rfc4627/dist/src/rfc4627_jsonrpc_sup.erl
@@ -0,0 +1,42 @@
+%% @author Tony Garnock-Jones <tonyg@kcbbs.gen.nz>
+%% @author LShift Ltd. <query@lshift.net>
+%% @copyright 2007, 2008 Tony Garnock-Jones and LShift Ltd.
+%% @license
+%%
+%% Permission is hereby granted, free of charge, to any person
+%% obtaining a copy of this software and associated documentation
+%% files (the "Software"), to deal in the Software without
+%% restriction, including without limitation the rights to use, copy,
+%% modify, merge, publish, distribute, sublicense, and/or sell copies
+%% of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be
+%% included in all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+%% EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+%% MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+%% NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+%% BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+%% CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+%% SOFTWARE.
+
+-module(rfc4627_jsonrpc_sup).
+-behaviour(supervisor).
+
+-export([start_link/0, init/1]).
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, _Arg = []).
+
+init([]) ->
+ {ok, {{one_for_one, 3, 10},
+ [{rfc4627_jsonrpc,
+ {rfc4627_jsonrpc, start_link, []},
+ permanent,
+ 10000,
+ worker,
+ [rfc4627_jsonrpc]}
+ ]}}.
View
15 contrib/erlang-rfc4627/dist/test/server_root/conf/httpd.conf
@@ -0,0 +1,15 @@
+ServerName localhost
+ServerRoot test/server_root
+DocumentRoot test/server_root/htdocs
+
+Port 5671
+
+Modules mod_alias mod_auth rfc4627_jsonrpc_inets mod_actions mod_cgi mod_responsecontrol mod_trace mod_range mod_head mod_include mod_dir mod_get mod_log mod_disk_log
+
+DirectoryIndex index.html
+
+JsonRpcAlias /rpc
+
+ErrorLog logs/error_log
+TransferLog logs/access_log
+SecurityLog logs/security_log
View
465 contrib/erlang-rfc4627/dist/test/server_root/conf/mime.types
@@ -0,0 +1,465 @@
+# This is a comment. I love comments.
+
+# MIME type Extension
+application/EDI-Consent
+application/EDI-X12
+application/EDIFACT
+application/activemessage
+application/andrew-inset ez
+application/applefile
+application/atomicmail
+application/batch-SMTP
+application/beep+xml
+application/cals-1840
+application/commonground
+application/cybercash
+application/dca-rft
+application/dec-dx
+application/dvcs
+application/eshop
+application/http
+application/hyperstudio
+application/iges
+application/index
+application/index.cmd
+application/index.obj
+application/index.response
+application/index.vnd
+application/iotp
+application/ipp
+application/isup
+application/font-tdpfr
+application/mac-binhex40 hqx
+application/mac-compactpro cpt
+application/macwriteii
+application/marc
+application/mathematica
+application/mathematica-old
+application/msword doc
+application/news-message-id
+application/news-transmission
+application/ocsp-request
+application/ocsp-response
+application/octet-stream bin dms lha lzh exe class so dll
+application/oda oda
+application/parityfec
+application/pdf pdf
+application/pgp-encrypted
+application/pgp-keys
+application/pgp-signature
+application/pkcs10
+application/pkcs7-mime
+application/pkcs7-signature
+application/pkix-cert
+application/pkix-crl
+application/pkixcmp
+application/postscript ai eps ps
+application/prs.alvestrand.titrax-sheet
+application/prs.cww
+application/prs.nprend
+application/qsig
+application/remote-printing
+application/riscos
+application/rtf
+application/sdp
+application/set-payment
+application/set-payment-initiation
+application/set-registration
+application/set-registration-initiation
+application/sgml
+application/sgml-open-catalog
+application/sieve
+application/slate
+application/smil smi smil
+application/timestamp-query
+application/timestamp-reply
+application/vemmi
+application/vnd.3M.Post-it-Notes
+application/vnd.FloGraphIt
+application/vnd.accpac.simply.aso
+application/vnd.accpac.simply.imp
+application/vnd.acucobol
+application/vnd.aether.imp
+application/vnd.anser-web-certificate-issue-initiation
+application/vnd.anser-web-funds-transfer-initiation
+application/vnd.audiograph
+application/vnd.businessobjects
+application/vnd.bmi
+application/vnd.canon-cpdl
+application/vnd.canon-lips
+application/vnd.claymore
+application/vnd.commerce-battelle
+application/vnd.commonspace
+application/vnd.comsocaller
+application/vnd.contact.cmsg
+application/vnd.cosmocaller
+application/vnd.cups-postscript
+application/vnd.cups-raster
+application/vnd.cups-raw
+application/vnd.ctc-posml
+application/vnd.cybank
+application/vnd.dna
+application/vnd.dpgraph
+application/vnd.dxr
+application/vnd.ecdis-update
+application/vnd.ecowin.chart
+application/vnd.ecowin.filerequest
+application/vnd.ecowin.fileupdate
+application/vnd.ecowin.series
+application/vnd.ecowin.seriesrequest
+application/vnd.ecowin.seriesupdate
+application/vnd.enliven
+application/vnd.epson.esf
+application/vnd.epson.msf
+application/vnd.epson.quickanime
+application/vnd.epson.salt
+application/vnd.epson.ssf
+application/vnd.ericsson.quickcall
+application/vnd.eudora.data
+application/vnd.fdf
+application/vnd.ffsns
+application/vnd.framemaker
+application/vnd.fsc.weblaunch
+application/vnd.fujitsu.oasys
+application/vnd.fujitsu.oasys2
+application/vnd.fujitsu.oasys3
+application/vnd.fujitsu.oasysgp
+application/vnd.fujitsu.oasysprs
+application/vnd.fujixerox.ddd
+application/vnd.fujixerox.docuworks
+application/vnd.fujixerox.docuworks.binder
+application/vnd.fut-misnet
+application/vnd.grafeq
+application/vnd.groove-account
+application/vnd.groove-identity-message
+application/vnd.groove-injector
+application/vnd.groove-tool-message
+application/vnd.groove-tool-template
+application/vnd.groove-vcard
+application/vnd.hhe.lesson-player
+application/vnd.hp-HPGL
+application/vnd.hp-PCL
+application/vnd.hp-PCLXL
+application/vnd.hp-hpid
+application/vnd.hp-hps
+application/vnd.httphone
+application/vnd.hzn-3d-crossword
+application/vnd.ibm.afplinedata
+application/vnd.ibm.MiniPay
+application/vnd.ibm.modcap
+application/vnd.informix-visionary
+application/vnd.intercon.formnet
+application/vnd.intertrust.digibox
+application/vnd.intertrust.nncp
+application/vnd.intu.qbo
+application/vnd.intu.qfx
+application/vnd.irepository.package+xml
+application/vnd.is-xpr
+application/vnd.japannet-directory-service
+application/vnd.japannet-jpnstore-wakeup
+application/vnd.japannet-payment-wakeup
+application/vnd.japannet-registration
+application/vnd.japannet-registration-wakeup
+application/vnd.japannet-setstore-wakeup
+application/vnd.japannet-verification
+application/vnd.japannet-verification-wakeup
+application/vnd.koan
+application/vnd.lotus-1-2-3
+application/vnd.lotus-approach
+application/vnd.lotus-freelance
+application/vnd.lotus-notes
+application/vnd.lotus-organizer
+application/vnd.lotus-screencam
+application/vnd.lotus-wordpro
+application/vnd.mcd
+application/vnd.mediastation.cdkey
+application/vnd.meridian-slingshot
+application/vnd.mif mif
+application/vnd.minisoft-hp3000-save
+application/vnd.mitsubishi.misty-guard.trustweb
+application/vnd.mobius.daf
+application/vnd.mobius.dis
+application/vnd.mobius.msl
+application/vnd.mobius.plc
+application/vnd.mobius.txf
+application/vnd.motorola.flexsuite
+application/vnd.motorola.flexsuite.adsi
+application/vnd.motorola.flexsuite.fis
+application/vnd.motorola.flexsuite.gotap
+application/vnd.motorola.flexsuite.kmr
+application/vnd.motorola.flexsuite.ttc
+application/vnd.motorola.flexsuite.wem
+application/vnd.mozilla.xul+xml
+application/vnd.ms-artgalry
+application/vnd.ms-asf
+application/vnd.ms-excel xls
+application/vnd.ms-lrm
+application/vnd.ms-powerpoint ppt
+application/vnd.ms-project
+application/vnd.ms-tnef
+application/vnd.ms-works
+application/vnd.mseq
+application/vnd.msign
+application/vnd.music-niff
+application/vnd.musician
+application/vnd.netfpx
+application/vnd.noblenet-directory
+application/vnd.noblenet-sealer
+application/vnd.noblenet-web
+application/vnd.novadigm.EDM
+application/vnd.novadigm.EDX
+application/vnd.novadigm.EXT
+application/vnd.osa.netdeploy
+application/vnd.palm
+application/vnd.pg.format
+application/vnd.pg.osasli
+application/vnd.powerbuilder6
+application/vnd.powerbuilder6-s
+application/vnd.powerbuilder7
+application/vnd.powerbuilder7-s
+application/vnd.powerbuilder75
+application/vnd.powerbuilder75-s
+application/vnd.previewsystems.box
+application/vnd.publishare-delta-tree
+application/vnd.pvi.ptid1
+application/vnd.pwg-xhtml-print+xml
+application/vnd.rapid
+application/vnd.s3sms
+application/vnd.seemail
+application/vnd.shana.informed.formdata
+application/vnd.shana.informed.formtemplate
+application/vnd.shana.informed.interchange
+application/vnd.shana.informed.package
+application/vnd.sss-cod