Skip to content

Commit b76e055

Browse files
committed
0.4.0-> 0.4.1 and Directive auto_upgrade_ws now sends error when meet
non-websocket upgrade request.
1 parent 45b660f commit b76e055

File tree

15 files changed

+188
-57
lines changed

15 files changed

+188
-57
lines changed

project.clj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
]
88
:plugins [[lein-junit "1.1.7"]
99
;[venantius/ultra "0.1.9"]
10-
[lein-sub "0.3.0"]
1110
]
12-
:sub ["nginx-tomcat8"]
1311
;; CLJ source code path
1412
:source-paths ["src/clojure"]
1513
:target-path "target/"

src/c/ngx_http_clojure_mem.c

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,11 @@ ngx_http_header_filter(ngx_http_request_t *r)
956956
r->header_only = 1;
957957
}
958958

959+
if (r->err_status) {
960+
r->headers_out.status = r->err_status;
961+
r->headers_out.status_line.len = 0;
962+
}
963+
959964
if (r->headers_out.last_modified_time != -1) {
960965
if (r->headers_out.status != NGX_HTTP_OK
961966
&& r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
@@ -1988,6 +1993,7 @@ static void nji_ngx_http_clojure_hijack_write_handler(ngx_http_request_t *r) {
19881993
ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) {
19891994
#if (NGX_HAVE_SHA1)
19901995
ngx_http_clojure_module_ctx_t *ctx;
1996+
ngx_int_t rc = NGX_OK;
19911997
ngx_table_elt_t *key = NULL;
19921998
ngx_table_elt_t *accept;
19931999
ngx_table_elt_t *cver = NULL;
@@ -1997,19 +2003,35 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) {
19972003
sha1_val.len = sizeof(degest) - 1;
19982004
sha1_val.data = (u_char *) degest;
19992005

2000-
ngx_http_clojure_add_const_header(r->headers_out.headers, "Sec-WebSocket-Version", "13");
2006+
ctx = ngx_http_get_module_ctx(r, ngx_http_clojure_module);
2007+
2008+
if (r->header_sent) {
2009+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "header was already sent, auto_upgrade_ws is on?");
2010+
if (ctx->wsctx) {
2011+
return NGX_OK;
2012+
}
2013+
rc = NGX_HTTP_BAD_REQUEST;
2014+
goto UPGRADE_DONE;
2015+
}
2016+
2017+
if (r->method != NGX_HTTP_GET || r->headers_in.upgrade == NULL
2018+
|| ngx_strcasecmp(r->headers_in.upgrade->value.data, (u_char*)"websocket") != 0) {
2019+
rc = NGX_HTTP_BAD_REQUEST;
2020+
goto UPGRADE_DONE;
2021+
}
20012022

2023+
ngx_http_clojure_add_const_header(r->headers_out.headers, "Sec-WebSocket-Version", "13");
20022024

20032025
ngx_http_clojure_get_header(r->headers_in.headers, "Sec-WebSocket-Version", cver);
20042026
if (cver == NULL || ngx_atoi(cver->value.data, cver->value.len) != 13) {
2005-
r->headers_out.status = NGX_HTTP_BAD_REQUEST;
2006-
return ngx_http_clojure_hijack_send_header(r, 0);
2027+
rc = NGX_HTTP_BAD_REQUEST;
2028+
goto UPGRADE_DONE;
20072029
}
20082030

20092031
ngx_http_clojure_get_header(r->headers_in.headers, "Sec-WebSocket-Key", key);
20102032
if (key == NULL) {
2011-
r->headers_out.status = NGX_HTTP_BAD_REQUEST;
2012-
return ngx_http_clojure_hijack_send_header(r, 0);
2033+
rc = NGX_HTTP_BAD_REQUEST;
2034+
goto UPGRADE_DONE;
20132035
}
20142036

20152037
r->headers_out.status = NGX_HTTP_SWITCHING_PROTOCOLS;
@@ -2029,22 +2051,36 @@ ngx_int_t ngx_http_clojure_websocket_upgrade(ngx_http_request_t * r) {
20292051

20302052
ngx_encode_base64(&accept->value, &sha1_val);
20312053

2032-
ctx = ngx_http_get_module_ctx(r, ngx_http_clojure_module);
20332054
if (ctx->wsctx == NULL) {
20342055
ctx->wsctx = ngx_pcalloc(r->pool, sizeof(ngx_http_clojure_websocket_ctx_t));
20352056
ctx->wsctx->ffm = 1;
20362057
ctx->wsctx->fin = 1;
20372058
}
20382059

2039-
return ngx_http_clojure_hijack_send_header(r, 0);
2060+
rc = ngx_http_clojure_hijack_send_header(r, 0);
2061+
2062+
UPGRADE_DONE:
2063+
if (ctx->hijacked_or_async) {
2064+
if (rc == NGX_HTTP_BAD_REQUEST) {
2065+
ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
2066+
}
2067+
}
2068+
return rc;
20402069
#else
2041-
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "nginx-clojure websocket support need compile config option --with-http_ssl_module");
2070+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "nginx-clojure websocket support need SHA1 enabled");
20422071
return NGX_ERROR;
20432072
#endif
20442073
}
20452074

2046-
static jlong JNICALL jni_ngx_http_clojure_websocket_upgrade(JNIEnv *env, jclass cls, jlong req) {
2047-
return ngx_http_clojure_websocket_upgrade((ngx_http_request_t *)(uintptr_t)req);
2075+
static jlong JNICALL jni_ngx_http_clojure_websocket_upgrade(JNIEnv *env, jclass cls, jlong req, jint flag) {
2076+
ngx_http_request_t * r = (ngx_http_request_t *)(uintptr_t)req;
2077+
if (!flag) {
2078+
if (r->headers_in.upgrade == NULL
2079+
|| ngx_strcasecmp(r->headers_in.upgrade->value.data, (u_char*)"websocket") != 0) {
2080+
return NGX_HTTP_BAD_REQUEST;
2081+
}
2082+
}
2083+
return ngx_http_clojure_websocket_upgrade(r);
20482084
}
20492085

20502086
/**
@@ -2236,8 +2272,11 @@ ngx_int_t ngx_http_clojure_hijack_send_header(ngx_http_request_t *r, ngx_int_t f
22362272
if (c->destroyed) {
22372273
return NGX_OK; /*TODO: return NGX_DONE?*/
22382274
}
2275+
if (rc == NGX_ERROR) {
2276+
return rc;
2277+
}
22392278
if (rc != NGX_OK) {
2240-
ngx_http_finalize_request(r, rc);
2279+
if (ctx->hijacked_or_async) ngx_http_finalize_request(r, rc);
22412280
return rc;
22422281
}
22432282
if (ctx->wsctx) {
@@ -2247,7 +2286,7 @@ ngx_int_t ngx_http_clojure_hijack_send_header(ngx_http_request_t *r, ngx_int_t f
22472286

22482287
return rc;
22492288
}else {
2250-
ngx_http_finalize_request(r, rc);
2289+
if (ctx->hijacked_or_async) ngx_http_finalize_request(r, rc);
22512290
return rc;
22522291
}
22532292
}
@@ -3137,10 +3176,15 @@ static jlong JNICALL jni_ngx_http_clojure_mem_set_variable(JNIEnv *env, jclass c
31373176

31383177
static jlong JNICALL jni_ngx_http_clojure_mem_inc_req_count(JNIEnv *env, jclass cls, jlong req, jlong detal) {
31393178
ngx_http_request_t *r = (ngx_http_request_t *)(uintptr_t) req;
3179+
ngx_http_clojure_module_ctx_t *ctx;
31403180
int n = 0;
3181+
ngx_http_clojure_get_ctx(r, ctx);
31413182
if (r->pool) {
31423183
jlong old = n = r->main->count;
31433184
n += (int)detal;
3185+
if (detal == 1) {
3186+
ctx->hijacked_or_async = 1;
3187+
}
31443188
r->main->count = n;
31453189
return old;
31463190
}
@@ -3587,6 +3631,7 @@ int ngx_http_clojure_pipe_exit_by_master() {
35873631
if (nc_jvm_worker_pipe_fds[0]) {
35883632
ngx_http_clojure_pipe_close(nc_jvm_worker_pipe_fds[0]);
35893633
ngx_http_clojure_pipe_close(nc_jvm_worker_pipe_fds[1]);
3634+
nc_jvm_worker_pipe_fds[0] = nc_jvm_worker_pipe_fds[1] = 0;
35903635
}
35913636
#endif
35923637
return NGX_OK;
@@ -3659,7 +3704,7 @@ int ngx_http_clojure_init_memory_util(ngx_http_core_srv_conf_t *cscf, ngx_http_c
36593704
{"ngx_http_hijack_send_chain", "(JJI)J", jni_ngx_http_hijack_send_chain},
36603705
{"ngx_http_hijack_set_async_timeout", "(JJ)V", jni_ngx_http_hijack_set_async_timeout},
36613706
{"ngx_http_clojure_add_listener", "(JLnginx/clojure/ChannelListener;Ljava/lang/Object;I)J", jni_ngx_http_clojure_add_listener},
3662-
{"ngx_http_clojure_websocket_upgrade", "(J)J", jni_ngx_http_clojure_websocket_upgrade},
3707+
{"ngx_http_clojure_websocket_upgrade", "(JI)J", jni_ngx_http_clojure_websocket_upgrade},
36633708
{"ngx_http_hijack_turn_on_event_handler", "(JI)V", jni_ngx_http_hijack_turn_on_event_handler},
36643709
{"ngx_http_hijack_read", "(JLjava/lang/Object;JJ)J", jni_ngx_http_hijack_read},
36653710
{"ngx_http_hijack_write", "(JLjava/lang/Object;JJ)J", jni_ngx_http_hijack_write},

src/c/ngx_http_clojure_mem.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
#include <inttypes.h>
3030
#endif
3131

32-
#define nginx_clojure_ver 4000 /*0.4.0*/
32+
#define nginx_clojure_ver 4001 /*0.4.0*/
3333

3434
/*the least jar version required*/
35-
#define nginx_clojure_required_rt_lver 4000
35+
#define nginx_clojure_required_rt_lver 4001
3636

37-
#define NGINX_CLOJURE_VER_NUM_STR "0.4.0"
37+
#define NGINX_CLOJURE_VER_NUM_STR "0.4.1"
3838

3939
#define NGINX_CLOJURE_VER "nginx-clojure/" NGINX_CLOJURE_VER_NUM_STR
4040

@@ -160,6 +160,7 @@ typedef struct {
160160
unsigned wait_for_header_filter : 1;
161161
unsigned pending_body_filter : 1;
162162
unsigned ignore_next_response : 1;
163+
unsigned hijacked_or_async : 1;
163164
#define NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_READ 1
164165
#define NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_WRITE 2
165166
#define NGX_HTTP_CLOJURE_EVENT_HANDLER_FLAG_NOKEEPALIVE 4
@@ -184,6 +185,7 @@ typedef struct {
184185
ctx->wait_for_header_filter = 0 ;\
185186
ctx->pending_body_filter = 0 ; \
186187
ctx->ignore_next_response = 0; \
188+
ctx->hijacked_or_async = 0; \
187189
ctx->event_handler_flag = 0; \
188190
ctx->wsctx = 0; \
189191
ctx->listeners = 0; \

src/c/ngx_http_clojure_module.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@ static ngx_int_t ngx_http_clojure_auto_detect_jvm(ngx_conf_t *cf) {
11771177
#endif
11781178
p = result;
11791179
i = 0;
1180-
while (i < sizeof(result)-1 && ((c = fgetc(fd)) != EOF) && c != '\r' && c != '\n') {
1180+
while (i < (int)sizeof(result)-1 && ((c = fgetc(fd)) != EOF) && c != '\r' && c != '\n') {
11811181
*p++ = c;
11821182
i++;
11831183
}
@@ -1235,7 +1235,7 @@ static ngx_int_t ngx_http_clojure_postconfiguration(ngx_conf_t *cf) {
12351235
return NGX_ERROR ;
12361236
}
12371237

1238-
if (!ngx_strcmp(mcf->jvm_path.data, "auto")) {
1238+
if (!ngx_http_clojure_is_embeded_by_jse && !ngx_strcmp(mcf->jvm_path.data, "auto")) {
12391239
if (ngx_http_clojure_auto_detect_jvm(cf) != NGX_OK) {
12401240
ngx_log_error(NGX_LOG_ERR, cf->log, 0, "can not find installed JRE/JDK");
12411241
return NGX_ERROR;
@@ -1343,6 +1343,8 @@ static ngx_int_t ngx_http_clojure_content_handler(ngx_http_request_t * r) {
13431343

13441344
ngx_http_clojure_init_ctx(ctx, -1, r);
13451345
ngx_http_set_ctx(r, ctx, ngx_http_clojure_module);
1346+
}else {
1347+
ctx->hijacked_or_async = 0;
13461348
}
13471349

13481350
if (lcf->always_read_body || (r->method & (NGX_HTTP_POST | NGX_HTTP_PUT | NGX_HTTP_PATCH))) {
@@ -1363,10 +1365,7 @@ static ngx_int_t ngx_http_clojure_content_handler(ngx_http_request_t * r) {
13631365
}
13641366
}else {
13651367

1366-
if (r->method == NGX_HTTP_GET
1367-
&& r->headers_in.upgrade != NULL
1368-
&& ngx_strcasecmp(r->headers_in.upgrade->value.data, (u_char*)"websocket") == 0
1369-
&& lcf->auto_upgrade_ws) {
1368+
if (lcf->auto_upgrade_ws) {
13701369
rc = ngx_http_clojure_websocket_upgrade(r);
13711370
if (rc != NGX_OK) {
13721371
return rc;
@@ -1466,6 +1465,7 @@ static ngx_int_t ngx_http_clojure_rewrite_handler(ngx_http_request_t *r) {
14661465
"ngx clojure rewrite (enter again) request: %" PRIu64 ", rc: %d", (jlong )(uintptr_t )r, NGX_DECLINED);
14671466
return ctx->phase_rc;
14681467
}else {
1468+
ctx->hijacked_or_async = 0;
14691469
ctx->phase = NGX_HTTP_REWRITE_PHASE;
14701470
rc = ngx_http_clojure_eval(lcf->rewrite_handler_id, r, 0);
14711471
if (rc != NGX_DONE) {
@@ -1525,6 +1525,7 @@ static ngx_int_t ngx_http_clojure_access_handler(ngx_http_request_t * r) {
15251525
"ngx clojure access (enter again) request: %" PRIu64 ", rc: %d", (jlong )(uintptr_t )r, NGX_DECLINED);
15261526
return ctx->phase_rc;
15271527
}else {
1528+
ctx->hijacked_or_async = 0;
15281529
ctx->phase = NGX_HTTP_ACCESS_PHASE;
15291530
rc = ngx_http_clojure_eval(lcf->access_handler_id, r, 0);
15301531
if (rc != NGX_DONE) {

src/clojure/nginx/clojure/core.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@
9898
the value of :on-close is a function like (fn[ch reason]...)
9999
the value of :on-error is a function like (fn[ch status])
100100
")
101+
(websocket-upgrade! [ch send-err-for-nonwebsocekt?]
102+
"Send upgrade headers and return true if upgrade success
103+
If `send-err-for-nonwebsocekt?` is true it will send error response.
104+
")
101105
(on-close! [ch attachment listener]
102106
"Add a close event listener.
103107
`attachement is a object which will be passed to listener when close event happens
@@ -175,6 +179,8 @@
175179
(onClose ;([c] (if on-close (on-close c "0")))
176180
([c status reason] (if on-close (on-close c (str status ":" reason)))))
177181
(onError [c status] (if on-error (on-error c (NginxClojureAsynSocket/errorCodeToString status)))))))
182+
(websocket-upgrade! [ch send-err-for-nonwebsocekt?]
183+
(.webSocketUpgrade ch ^boolean send-err-for-nonwebsocekt?))
178184
(get-context [ch]
179185
(.getContext ch))
180186
(set-context! [ch ctx]

src/java/nginx/clojure/MiniConstants.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,8 +371,8 @@ public class MiniConstants {
371371
public static int NGX_HTTP_CLOJURE_MEM_IDX_END = 255;
372372

373373
//nginx clojure java runtime required the lowest version of nginx-clojure c module
374-
public static long NGINX_CLOJURE_RT_REQUIRED_LVER = 4000;
375-
public static long NGINX_CLOJURE_RT_VER = 4000;
374+
public static long NGINX_CLOJURE_RT_REQUIRED_LVER = 4001;
375+
public static long NGINX_CLOJURE_RT_VER = 4001;
376376

377377
//ngx_core.h
378378
public final static int NGX_OK = 0;

src/java/nginx/clojure/NginxClojureRT.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,16 @@ public static void addListener(long r, ChannelListener listener, Object data, in
208208
}
209209
}
210210

211-
public native static long ngx_http_clojure_websocket_upgrade(long req);
211+
public static long ngx_http_clojure_websocket_upgrade(long req) {
212+
return ngx_http_clojure_websocket_upgrade(req, 1);
213+
}
214+
215+
/**
216+
* flag can be either of
217+
* 0 do nothing for non-websocket request
218+
* 1 error for non-websocket request
219+
*/
220+
public native static long ngx_http_clojure_websocket_upgrade(long req, int flag);
212221

213222
/**
214223
* flag can be either of or combined of

src/java/nginx/clojure/NginxHttpServerChannel.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
import java.util.Collections;
1717
import java.util.Map;
1818
import java.util.Map.Entry;
19+
import java.util.concurrent.Callable;
20+
import java.util.concurrent.ExecutionException;
21+
import java.util.concurrent.FutureTask;
1922

2023
import nginx.clojure.java.NginxJavaResponse;
2124
import nginx.clojure.net.NginxClojureAsynSocket;
@@ -582,4 +585,25 @@ public void run() {
582585
NginxClojureRT.ngx_http_hijack_set_async_timeout(request.nativeRequest(), asyncTimeout);
583586
}
584587
}
588+
589+
public boolean webSocketUpgrade(final boolean sendErrorForNonWebSocket) {
590+
if (Thread.currentThread() != NginxClojureRT.NGINX_MAIN_THREAD) {
591+
FutureTask<Boolean> task = new FutureTask<Boolean>(new Callable<Boolean>() {
592+
@Override
593+
public Boolean call() throws Exception {
594+
return NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0;
595+
}
596+
});
597+
NginxClojureRT.postPollTaskEvent(task);
598+
try {
599+
return task.get();
600+
} catch (InterruptedException e) {
601+
throw new RuntimeException("webSocketUpgrade Interrupted", e);
602+
} catch (ExecutionException e) {
603+
throw new RuntimeException("webSocketUpgrade Execution error", e.getCause());
604+
}
605+
}else {
606+
return NginxClojureRT.ngx_http_clojure_websocket_upgrade(request.nativeRequest(), sendErrorForNonWebSocket ? 1 : 0) == 0;
607+
}
608+
}
585609
}

test/clojure/nginx/clojure/ring_handlers_for_test.clj

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,14 @@
150150
(swap! long-polling-subscribers assoc ch req))))
151151
(GET "/ws-echo" []
152152
(fn [^NginxRequest req]
153-
(-> req
154-
(hijack! true)
155-
(add-listener! { :on-open (fn [ch] (log "uri:%s, on-open!" (:uri req)))
156-
:on-message (fn [ch msg rem?] (send! ch msg (not rem?) false))
157-
:on-close (fn [ch reason] (log "uri:%s, on-close:%s" (:uri req) reason))
158-
:on-error (fn [ch error] (log "uri:%s, on-error:%s" (:uri req) error))
159-
}))))
153+
(let [ch (hijack! req true)]
154+
(if (websocket-upgrade! ch true)
155+
(add-listener! ch { :on-open (fn [ch] (log "uri:%s, on-open!" (:uri req)))
156+
:on-message (fn [ch msg rem?] (send! ch msg (not rem?) false))
157+
:on-close (fn [ch reason] (log "uri:%s, on-close:%s" (:uri req) reason))
158+
:on-error (fn [ch error] (log "uri:%s, on-error:%s" (:uri req) error))
159+
})
160+
{}))))
160161
(GET "/ws-remote" []
161162
(fn [^NginxRequest req]
162163
(-> req

test/clojure/nginx/clojure/test_all.clj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,16 @@
10131013
(debug-println "===================/java-ws/echo=======================")
10141014
(ws/send-msg ws-client msg)
10151015
(is (= msg @result))))
1016+
(testing "/java-ws/nu-echo"
1017+
(let [base (str "ws://" *host* ":" *port* "/java-ws/nu-echo")
1018+
msg "hello, nginx-clojure & websocket!"
1019+
result (promise)
1020+
ws-client (ws/connect base
1021+
:on-receive #(deliver result %))
1022+
]
1023+
(debug-println "===================/java-ws/nu-echo=======================")
1024+
(ws/send-msg ws-client msg)
1025+
(is (= msg @result))))
10161026
(testing "/ringCompojure/ws-echo"
10171027
(let [base (str "ws://" *host* ":" *port* "/ringCompojure/ws-echo")
10181028
msg "hello, nginx-clojure & websocket!"

test/java/nginx/clojure/java/WSEcho.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ public class WSEcho implements NginxJavaRingHandler {
1313
@Override
1414
public Object[] invoke(Map<String, Object> request) {
1515
NginxJavaRequest r = (NginxJavaRequest)request;
16-
if (!r.isWebSocket()) {
17-
return NginxJavaHandler.NOT_FOUND_RESPONSE;
18-
}
1916
NginxHttpServerChannel sc = r.hijack(true);
17+
18+
//If we use nginx directive `auto_upgrade_ws on;`, these three lines can be omitted.
19+
if (!sc.webSocketUpgrade(true)) {
20+
return null;
21+
}
22+
2023
sc.addListener(sc, new MessageAdapter<NginxHttpServerChannel>() {
2124
int total = 0;
2225
@Override

0 commit comments

Comments
 (0)