{"id":21464130,"url":"https://github.com/vaelatern/necessary-evil","last_synced_at":"2025-12-12T01:37:44.103Z","repository":{"id":62433792,"uuid":"1121674","full_name":"Vaelatern/necessary-evil","owner":"Vaelatern","description":"XML-RPC implemented as Ring HTTP handlers for Clojure","archived":false,"fork":false,"pushed_at":"2018-10-11T20:08:49.000Z","size":145,"stargazers_count":61,"open_issues_count":0,"forks_count":8,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-10-12T08:58:32.504Z","etag":null,"topics":["clojure","ring","xml-rpc"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Vaelatern.png","metadata":{"files":{"readme":"README.markdown","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-11-29T09:36:02.000Z","updated_at":"2024-08-20T20:53:20.000Z","dependencies_parsed_at":"2022-11-01T21:01:37.834Z","dependency_job_id":null,"html_url":"https://github.com/Vaelatern/necessary-evil","commit_stats":null,"previous_names":["brehaut/necessary-evil"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Vaelatern/necessary-evil","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vaelatern%2Fnecessary-evil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vaelatern%2Fnecessary-evil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vaelatern%2Fnecessary-evil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vaelatern%2Fnecessary-evil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Vaelatern","download_url":"https://codeload.github.com/Vaelatern/necessary-evil/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Vaelatern%2Fnecessary-evil/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27673696,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-11T02:00:11.302Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","ring","xml-rpc"],"created_at":"2024-11-23T07:29:51.004Z","updated_at":"2025-12-12T01:37:44.065Z","avatar_url":"https://github.com/Vaelatern.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# necessary-evil\n\n*necessary-evil* is an implementation of [XML-RPC](http://xml-rpc.com/)\nbuilt on top of the [ring http\nlibrary](https://github.com/ring-clojure/ring) for Clojure. XML-RPC is a\nbit nasty, but it is the basis of a number of other standards such as\ncertain blogging APIs and Ping Back.\n\n`necessary-evil` will only work with Java 6+, and Clojure 1.2.1+ \n\n## Usage\n\n```clojure\n(require '[necessary-evil.core :as xml-rpc])\n```\n\nMaking a client request is very simple:\n\n```clojure\n(xml-rpc/call \"http://example.com/rpc\" :hello \"World\") \n```\n\nThis will either return a clojure data structure or, if there is a fault, a `necessary-evil.methodresponse.Fault` record. For the example above, you might expect something like `\"Hello, World!\"` to be returned. See xml-rpc mappings below for details of how xml-rpc data is converted to clojure data and vice versa.\n\nCall accepts the arguments to the remote method as varags after the method name.\n\nHere is a simple hello world request handler:\n\n```clojure\n(use '[ring.adapter.jetty :only [run-jetty]]) \n    \n(def handler (xml-rpc/end-point \n     {:hello (fn hello \n        ([] (hello \"World\"))\n        ([name] (str \"Hello, \" name \"!\")))}))\n    \n(run-jetty handler {:port 3000 :join? false})\n```\n\nMethods are :keyword iFn pairs in the method map passed to end-point. \n\nAs XML-RPC requests are always HTTP POST requests, necessary-evil implements a very bare bones GET handler that returns a comma separated list of method names. \n\nThe handler generated by end-point should work properly with any other ring handler, and should play nice with middleware or any other ring library such as [compojure](https://github.com/weavejester/compojure/) or [moustache](https://github.com/cgrand/moustache).\n\n### Compojure example:\n\nThe following is a trivial example to attach an xml-rpc endpoint into a hello world compojure application:\n\n\n```clojure\n(require '[necessary-evil.core :as xmlrpc])  \n(use '[ring.adapter.jetty :only [run-jetty]])   \n(use '[compojure.core :only [defroutes GET ANY]])\n    \n(def ep (xmlrpc/end-point \n          {:hello (fn [n] (str \"Hello, \" n \"!\"))}))\n\n(defroutes handler \n  (GET \"/hello\" [] \"Hello!\")\n  (ANY \"/xml\" [] ep))\n\n(run-jetty #'handler {:port 3000 :join? false})\n```\n\nIn this application `/` is a 404, `/hello` returns \"Hello!\", and `/xml` is the xmlrpc handler.\n\n### Moustache example:\n\nThis snippet implements the same server as the one above for Compojure:\n\n\n```clojure\n(require '[necessary-evil.core :as xmlrpc])  \n(use '[ring.adapter.jetty :only [run-jetty]])   \n(use '[net.cgrand.moustache :only [app]])                                        \n    \n(def ep (xmlrpc/end-point \n          {:hello (fn [n] (str \"Hello, \" n \"!\"))}))\n(def handler\n  (app [\"hello\"] {:get \"Hello!\"} \n       [\"xml\"] ep))\n    \n(run-jetty #'handler {:port 3000 :join? false})\n```\n\nJust as in the compojure example above,  `/` is a 404, `/hello` returns \"Hello!\", and `/xml` is the xmlrpc handler.\n\n### xml-rpc mappings\n\nThese tables describes the mapping of clojure datastructures and types\nto XML-RPC types. Note that as of version 2.0.0 these are no longer symmetric operations. \n\n\u003ctable style=\"width: 100%\"\u003e\n    \u003cthead\u003e\n    \u003ctr\u003e\u003cth colspan=\"2\"\u003eXML-RPC \u0026rarr; Clojure\u003c/th\u003e\u003c/tr\u003e\n    \u003ctr\u003e\u003cth\u003eXML-RPC Element\u003c/th\u003e\u003cth\u003eClojure or Java type\u003c/th\u003e\u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n        \u003ctr\u003e\u003ctd\u003earray\u003c/td\u003e\u003ctd\u003eclojure.lang.IPersistentVector\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ebase64\u003c/td\u003e\u003ctd\u003ebyte-array\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003eboolean\u003c/td\u003e\u003ctd\u003ejava.lang.Boolean\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003edateTime.iso8601\u003c/td\u003e\u003ctd\u003eorg.joda.time.DateTime\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003edouble\u003c/td\u003e\u003ctd\u003ejava.lang.Double\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ei4\u003c/td\u003e\u003ctd\u003ejava.lang.Integer\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003eint\u003c/td\u003e\u003ctd\u003ejava.lang.Integer\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003estruct\u003c/td\u003e\u003ctd\u003eclojure.lang.IPersistantMap — \u003cem\u003eclojure.lang.Keyword keys\u003c/em\u003e\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003e\u003cem\u003eno element\u003c/em\u003e\u003c/td\u003e\u003ctd\u003ejava.lang.String\u003c/td\u003e\u003c/tr\u003e\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n\u003ctable style=\"width: 100%\"\u003e\n    \u003cthead\u003e\n    \u003ctr\u003e\u003cth colspan=\"2\"\u003eClojure \u0026rarr; XML-RPC\u003c/th\u003e\u003c/tr\u003e\n    \u003ctr\u003e\u003cth\u003eClojure or Java type\u003c/th\u003e\u003cth\u003eXML-RPC Element\u003c/th\u003e\u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody\u003e\n        \u003ctr\u003e\u003ctd\u003ebyte-array\u003c/td\u003e\u003ctd\u003ebase64\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003eclojure.lang.IPersistantMap — \u003cem\u003eclojure.lang.Keyword keys\u003c/em\u003e\u003c/td\u003e\u003ctd\u003estruct\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003eclojure.lang.Sequential\u003c/td\u003e\u003ctd\u003earray\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ejava.lang.Boolean\u003c/td\u003e\u003ctd\u003eboolean\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ejava.lang.Double\u003c/td\u003e\u003ctd\u003edouble\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ejava.lang.Integer\u003c/td\u003e\u003ctd\u003eint\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\n            \u003ctd\u003ejava.lang.Long\u003c/td\u003e\n            \u003ctd\u003eint – \u003cem\u003eLongs that are greater than\nInteger/MAX_VALUE will cause an exception to be thrown.\u003c/em\u003e\n            \u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ejava.lang.String\u003c/td\u003e\u003ctd\u003e\u003cem\u003estring\u003c/em\u003e\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003ejava.util.Date\u003c/td\u003e\u003ctd\u003edateTime.iso8601\u003c/td\u003e\u003c/tr\u003e\n        \u003ctr\u003e\u003ctd\u003eorg.joda.time.DateTime\u003c/td\u003e\u003ctd\u003edateTime.iso8601\u003c/td\u003e\u003c/tr\u003e\n    \u003c/tbody\u003e\n\u003c/table\u003e\n\n**Note:** `nil` is conspicuously absent from the list of types; this is because the spec for xml-rpc itself does not include any canonical representation.\n\n#### Implementing additional mappings.\n\nIt is possible to extend the support to additional data types trivially. All the details of parsing and unparsing the various value types is handled in the `necessary-evil.value` namespace with the multimethod `parse-value` and the protocol `ValueTypeElem`. Simple implement the appropriate pair for each of these in your own code.\n\nKeep in mind that if you specify a mapping from a new Clojure type to an existing xmlrpc type that the mapping will be asymmetric. If you add additional xmlrpc types keep in mind that the xmlrpc implementation at the other end will also need to know how to serialize and deserialize the type. \n\n## Primary API\n\nThe following the main API functions that you will use as a consumer of the library. \n\n * `necessary-evil.core/end-point` — Defines a Ring handler that acts as an XML-RPC end-point. See above for examples.\n * `necessary-evil.core/call` — Calls an XML-RPC function at a given end point.\n * `necessary-evil.core/call*` — Calls an XML-RPC function at a given end point. Allows for more control than `necessary-evil.core/call`.\n * `necessary-evil.fault/fault` — Creates a new fault, use this if you need to return an error condition to the caller.\n * `necessary-evil.fault/fault?` — Predicate that tests a value for being a Fault record.\n * `necessary-evil.fault/attempt-all` — A comprehension form to make it easier to work with potentially Fault returning functions. For more detail on this macro see my [Error Monads](http://brehaut.net/blog/2011/error_monads#attempt_all) and [Error Monads Revisited ](http://brehaut.net/blog/2011/error_monads_revisited) blog posts.\n\n## Changes from 2.0.0 to 2.0.1\n\n * Updated dependencies to newer versions\n\n## Changes from 1.2.2 to 2.0.0\n\nDespite the big jump in version numbers relatively small changes have occured.\n\n * Updated to work with Clojure 1.2.1, 1.3.0 and new contrib modules.\n   * Class name munging of '-' characters in Record names caused problems using prior necessary-evil 1.x \n     to have import errors on Clojure 1.2.1 and 1.3.\n * Changed mappings in the `ValueTypeElem` protocol:\n   * Instead of `clojure.lang.PersistanceVector`, any\n     `clojure.lang.Sequential` implementor will be serialized to an array; this includes lists and\n     lazy sequences.\n   * `Long`s are now serialized as `Integer`s (and must not exceed `Integer.MAX_VALUE` as the xmlrpc spec only allows for 4 byte signed ints).     \n   * `java.util.Date` objects are now serialized to time\n * `call*` function added to `necessary-evil.core` to add more fine grained control to the http request.\n   `call` now uses `call*` under the hood. The major difference is that `call*` takes the remote functions arguments as a sequence, and has keyword options for the configurable things.\n * String values no longer normalize whitespace: You may now find you have to remove newlines or other whitespace yourself.\n * Type hints to avoid reflection added across all namespaces.\n \n \nNote that the serialization and deserialization processes are now *asymmetric*: For example in a round trip a list will return as vector, Java dates will return as Joda time dates and longs as ints.\n\n## Changes from 1.2.1 to 1.2.2\n\n * Fixed issue #4 where empty string values that have no type elements\n   would mysteriously disappear.\n\n## Changes from 1.2 to 1.2.1\n \n * Addes UTF-8 character set to content type headers for both client and server. Thanks to Laurent Petit for this \n   fix.\n\n## Changes from 1.1 to 1.2\n\n * Fixes a bug with values containing XML special characters not being escaped\n * Support for [Null support extension](http://ontosys.com/xml-rpc/extensions.php) with the `necessary-evil.value/allow-nils` form.\n * Uses non-snapshot `clj-time`\n\n## Changes from 1.0 to 1.1\n\n * `fault` handling code is now in its own namespace: `necessary-evil.fault`\n * `necessary-evil.fault` now includes `attempt-all` macro to stream line \n     writing code that may generate faults in multiple stages.\n\n## Notes\n\nLaurent Petit has a recipe for making [Make a SSL certificate visible to your app](https://gist.github.com/e3b6d07cbc8b26373deb). This may be relevant if you wish to secure your api with HTTPS and are not running your application behind another web server.\n\n## Thanks\n\nThanks to the following people for their feedback and assistance:\n\n * [Raynes](https://github.com/Raynes/) (also for letting me\n steal from his `clj-xmlrpc` code).\n * [Amalloy](https://github.com/amalloy/) \n * [kumarshantanu](https://bitbucket.org/kumarshantanu)\n * [semperos](https://github.com/semperos)\n * [laurentpetit](https://github.com/laurentpetit)\n\n## License\n\nCopyright (C) 2010, 2011 Andrew Brehaut\n\nDistributed under the Eclipse Public License, the same as Clojure.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaelatern%2Fnecessary-evil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvaelatern%2Fnecessary-evil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaelatern%2Fnecessary-evil/lists"}