#include /* Clojure/ring JVM handler to run a ring app you need to load the clojure jar, a clojure script and specify a namspace:handler app ./uwsgi --http :9090 --http-modifier1 8 --http-modifier2 1 --jvm-classpath clojuretest/lib/clojure-1.3.0.jar --ring-load clojuretest/src/clojuretest/core.clj --ring-app clojuretest.core:handler TODO app mountpoints check if loading compiled classes works */ #define UWSGI_JVM_REQUEST_HANDLER_RING 1 extern struct uwsgi_jvm ujvm; struct uwsgi_ring { struct uwsgi_string_list *scripts; char *app; jobject handler; jobject keyword; jobject into; // invoke with 1 arg jmethodID invoke1; // invoke with 2 args jmethodID invoke2; jclass Associative; jclass PersistentArrayMap; } uring; static struct uwsgi_option uwsgi_ring_options[] = { {"ring-load", required_argument, 0, "load the specified clojure script", uwsgi_opt_add_string_list, &uring.scripts, 0}, {"clojure-load", required_argument, 0, "load the specified clojure script", uwsgi_opt_add_string_list, &uring.scripts, 0}, {"ring-app", required_argument, 0, "map the specified ring application (syntax namespace:function)", uwsgi_opt_set_str, &uring.app, 0}, {0, 0, 0, 0}, }; static jobject uwsgi_ring_invoke1(jobject o, jobject arg1) { return uwsgi_jvm_call_object(o, uring.invoke1, arg1); } static jobject uwsgi_ring_invoke2(jobject o, jobject arg1, jobject arg2) { return uwsgi_jvm_call_object(o, uring.invoke2, arg1, arg2); } // here we create a PersistentArrayMap empty object (we use that for clojure "into") static jobject uwsgi_ring_associative() { // optimization static jmethodID mid = 0; if (!mid) { mid = uwsgi_jvm_get_method_id(uring.PersistentArrayMap, "", "()V"); if (!mid) return NULL; } jobject o = (*ujvm_env)->NewObject(ujvm_env, uring.PersistentArrayMap, mid); if (uwsgi_jvm_exception()) { return NULL; } return o; } // create a new clojure keyword static jobject uwsgi_ring_keyword(char *key, size_t len) { jobject j_key = uwsgi_jvm_str(key, len); if (!j_key) return NULL; jobject kw = uwsgi_ring_invoke1(uring.keyword, j_key); uwsgi_jvm_local_unref(j_key); return kw; } // add a string item to the ring request map static int uwsgi_ring_request_item_add(jobject hm, char *key, size_t keylen, char *value, size_t vallen) { jobject j_key = uwsgi_ring_keyword(key, keylen); if (!j_key) return -1; jobject j_value = uwsgi_jvm_str(value, vallen); if (!j_value) { uwsgi_jvm_local_unref(j_key); return -1; } int ret = uwsgi_jvm_hashmap_put(hm, j_key, j_value); uwsgi_jvm_local_unref(j_key); uwsgi_jvm_local_unref(j_value); return ret; } static int uwsgi_ring_request_item_add_body(jobject hm, char *key, size_t keylen) { jobject j_key = uwsgi_ring_keyword(key, keylen); if (!j_key) return -1; jobject j_value = uwsgi_jvm_request_body_input_stream(); if (!j_value) { uwsgi_jvm_local_unref(j_key); return -1; } int ret = uwsgi_jvm_hashmap_put(hm, j_key, j_value); uwsgi_jvm_local_unref(j_key); uwsgi_jvm_local_unref(j_value); return ret; } // add a keyword item to the ring request map static int uwsgi_ring_request_item_add_keyword(jobject hm, char *key, size_t keylen, char *value, size_t vallen) { jobject j_key = uwsgi_ring_keyword(key, keylen); if (!j_key) return -1; char *lc_value = uwsgi_malloc(vallen); char *ptr = lc_value; size_t i; for(i=0;imethod, wsgi_req->method_len)) goto end; if (uwsgi_ring_request_item_add(hm, "uri", 3, wsgi_req->path_info, wsgi_req->path_info_len)) goto end; if (uwsgi_ring_request_item_add(hm, "server-name", 11, wsgi_req->host, wsgi_req->host_len)) goto end; // server-port is required !!! uint16_t server_port_len = 0; char *server_port = uwsgi_get_var(wsgi_req, "SERVER_PORT", 11, &server_port_len); if (!server_port) goto end; if (uwsgi_ring_request_item_add_num(hm, "server-port", 11, uwsgi_str_num(server_port, server_port_len))) goto end; if (wsgi_req->scheme_len > 0) { if (uwsgi_ring_request_item_add_keyword(hm, "scheme", 6, wsgi_req->scheme, wsgi_req->scheme_len)) goto end; } else { if (uwsgi_ring_request_item_add_keyword(hm, "scheme", 6, "http", 4)) goto end; } if (uwsgi_ring_request_item_add(hm, "remote-addr", 11, wsgi_req->remote_addr, wsgi_req->remote_addr_len)) goto end; if (wsgi_req->query_string_len > 0) { if (uwsgi_ring_request_item_add(hm, "query-string", 12, wsgi_req->query_string, wsgi_req->query_string_len)) goto end; } if (wsgi_req->content_type_len) { if (uwsgi_ring_request_item_add(hm, "content-type", 12, wsgi_req->content_type, wsgi_req->content_type_len)) goto end; } if (wsgi_req->post_cl > 0) { if (uwsgi_ring_request_item_add_num(hm, "content-length", 14, wsgi_req->post_cl)) goto end; } // generate :headers jobject req_headers = uwsgi_jvm_hashmap(); if (!req_headers) goto end; int i; for(i=0;ivar_cnt;i++) { char *hk = wsgi_req->hvec[i].iov_base; uint16_t hk_l = wsgi_req->hvec[i].iov_len; char *hv = wsgi_req->hvec[i+1].iov_base; uint16_t hv_l = wsgi_req->hvec[i+1].iov_len; if (!uwsgi_starts_with(hk, hk_l, "HTTP_", 5)) { if (uwsgi_ring_request_header_add(req_headers,hk+5,hk_l-5, hv, hv_l)) goto hend; } else if (!uwsgi_strncmp(hk, hk_l, "CONTENT_TYPE", 12)) { if (uwsgi_ring_request_header_add(req_headers,hk,hk_l, hv, hv_l)) goto hend; } else if (!uwsgi_strncmp(hk, hk_l, "CONTENT_LENGTH", 14)) { if (uwsgi_ring_request_header_add(req_headers, hk,hk_l, hv, hv_l)) goto hend; } i++; } jobject h_empty_request = uwsgi_ring_associative(); if (!h_empty_request) goto hend; // convert the req_headers HashMap to Associative jobject clj_req_headers = uwsgi_ring_invoke2(uring.into, h_empty_request, req_headers); if (!clj_req_headers) { uwsgi_jvm_local_unref(h_empty_request); goto hend; } uwsgi_jvm_local_unref(h_empty_request); // add :headers if (uwsgi_ring_request_item_add_obj(hm, "headers", 7, clj_req_headers)) { uwsgi_jvm_local_unref(clj_req_headers); hend: uwsgi_jvm_local_unref(req_headers); goto end; } uwsgi_jvm_local_unref(clj_req_headers); uwsgi_jvm_local_unref(req_headers); // add :body input stream if (uwsgi_ring_request_item_add_body(hm, "body", 4)) goto end; // *** END OF REQUEST GENERATION *** // convert the HashMap to Associative request = uwsgi_ring_invoke2(uring.into, empty_request, hm); if (!request) goto end; response = uwsgi_ring_invoke1(uring.handler, request); if (!response) goto end; if (!uwsgi_jvm_object_is_instance(response, uring.Associative)) { uwsgi_log("invalid ring response type, need to implement: clojure.lang.Associative\n"); goto end; } r_status = uwsgi_ring_response_get(response, "status", 6); if (!r_status) goto end; if (!uwsgi_jvm_object_is_instance(r_status, ujvm.long_class) && !uwsgi_jvm_object_is_instance(r_status, ujvm.int_class)) { uwsgi_log("invalid ring response status type, must be: java.lang.Long\n"); goto end; } long n_status = uwsgi_jvm_number2c(r_status); if (n_status == -1) goto end; if (uwsgi_num2str2(n_status, status_str) != 3) { goto end; } if (uwsgi_response_prepare_headers(wsgi_req, status_str, 3)) goto end; r_headers = uwsgi_ring_response_get(response, "headers", 7); if (!r_headers) goto end; if (!uwsgi_jvm_object_is_instance(r_headers, uring.Associative)) { uwsgi_log("invalid ring response headers type, need to implement: clojure.lang.Associative\n"); goto end; } entries = uwsgi_ring_Associative_iterator(r_headers); if (!entries) goto end; if (uwsgi_jvm_iterator_to_response_headers(wsgi_req, entries)) { goto end; } r_body = uwsgi_ring_response_get(response, "body", 4); if (!r_body) goto end; if (uwsgi_jvm_object_to_response_body(wsgi_req, r_body)) { uwsgi_log("unsupported clojure/ring body type\n"); } end: // destroy the request map and the response uwsgi_jvm_local_unref(hm); uwsgi_jvm_local_unref(empty_request); if (request) { uwsgi_jvm_local_unref(request); } if (entries) { uwsgi_jvm_local_unref(entries); } if (r_status) { uwsgi_jvm_local_unref(r_status); } if (r_headers) { uwsgi_jvm_local_unref(r_headers); } if (r_body) { uwsgi_jvm_local_unref(r_body); } if (response) { uwsgi_jvm_local_unref(response); } return UWSGI_OK; } static int uwsgi_ring_setup() { uwsgi_log("loading clojure environment...\n"); jclass clojure = uwsgi_jvm_class("clojure/lang/RT"); if (!clojure) { exit(1); } jclass clojure_var_class = uwsgi_jvm_class("clojure/lang/Var"); if (!clojure_var_class) { exit(1); } uring.Associative = uwsgi_jvm_class("clojure/lang/Associative"); if (!uring.Associative) { exit(1); } // we use that for allocating the empty associative passed to "into" uring.PersistentArrayMap = uwsgi_jvm_class("clojure/lang/PersistentArrayMap"); if (!uring.PersistentArrayMap) { exit(1); } jmethodID clojure_loadresourcescript = uwsgi_jvm_get_static_method_id(clojure, "loadResourceScript", "(Ljava/lang/String;)V"); if (!clojure_loadresourcescript) { exit(1); } struct uwsgi_string_list *usl = uring.scripts; while(usl) { if (uwsgi_jvm_call_static(clojure, clojure_loadresourcescript, uwsgi_jvm_str(usl->value, 0))) { exit(1); } usl = usl->next; } jmethodID clojure_var = uwsgi_jvm_get_static_method_id(clojure, "var", "(Ljava/lang/String;Ljava/lang/String;)Lclojure/lang/Var;"); if (!clojure_var) { exit(1); } uring.keyword = uwsgi_jvm_call_object_static(clojure, clojure_var, uwsgi_jvm_str("clojure.core", 0), uwsgi_jvm_str("keyword", 0)); if (!uring.keyword) { exit(1); } uring.into = uwsgi_jvm_call_object_static(clojure, clojure_var, uwsgi_jvm_str("clojure.core", 0), uwsgi_jvm_str("into", 0)); if (!uring.into) { exit(1); } char *namespace = uwsgi_str(uring.app); char *colon = strchr(namespace, '/'); if (!colon) { colon = strchr(namespace, ':'); if (!colon) { uwsgi_log("invalid ring application namespace/handler\n"); exit(1); } } *colon = 0; uring.handler = uwsgi_jvm_call_object_static(clojure, clojure_var, uwsgi_jvm_str(namespace, 0), uwsgi_jvm_str(colon+1, 0)); if (!uring.handler) { exit(1); } uring.invoke1 = uwsgi_jvm_get_method_id(clojure_var_class, "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;"); if (!uring.invoke1) { exit(1); } uring.invoke2 = uwsgi_jvm_get_method_id(clojure_var_class, "invoke", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); if (!uring.invoke2) { exit(1); } uwsgi_log("clojure/ring app loaded\n"); return 0; } static int uwsgi_ring_init() { if (!uring.app) return 0; if (uwsgi_jvm_register_request_handler(UWSGI_JVM_REQUEST_HANDLER_RING, uwsgi_ring_setup, uwsgi_ring_request)) { exit(1); } return 0; } struct uwsgi_plugin ring_plugin = { .name = "ring", .options = uwsgi_ring_options, .init = uwsgi_ring_init, };