unicorn-fork 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- metadata +266 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
/*
|
2
|
+
* This is only intended for use inside a unicorn worker, nowhere else.
|
3
|
+
* EPOLLEXCLUSIVE somewhat mitigates the thundering herd problem for
|
4
|
+
* mostly idle processes since we can't use blocking accept4.
|
5
|
+
* This is NOT intended for use with multi-threaded servers, nor
|
6
|
+
* single-threaded multi-client ("C10K") servers or anything advanced
|
7
|
+
* like that. This use of epoll is only appropriate for a primitive,
|
8
|
+
* single-client, single-threaded servers like unicorn that need to
|
9
|
+
* support SIGKILL timeouts and parent death detection.
|
10
|
+
*/
|
11
|
+
#if defined(HAVE_EPOLL_CREATE1)
|
12
|
+
# include <sys/epoll.h>
|
13
|
+
# include <errno.h>
|
14
|
+
# include <ruby/io.h>
|
15
|
+
# include <ruby/thread.h>
|
16
|
+
#endif /* __linux__ */
|
17
|
+
|
18
|
+
#if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1)
|
19
|
+
# define USE_EPOLL (1)
|
20
|
+
#else
|
21
|
+
# define USE_EPOLL (0)
|
22
|
+
#endif
|
23
|
+
|
24
|
+
#if USE_EPOLL
|
25
|
+
#if defined(HAVE_RB_IO_DESCRIPTOR) /* Ruby 3.1+ */
|
26
|
+
# define my_fileno(io) rb_io_descriptor(io)
|
27
|
+
#else /* Ruby <3.1 */
|
28
|
+
static int my_fileno(VALUE io)
|
29
|
+
{
|
30
|
+
rb_io_t *fptr;
|
31
|
+
GetOpenFile(io, fptr);
|
32
|
+
rb_io_check_closed(fptr);
|
33
|
+
return fptr->fd;
|
34
|
+
}
|
35
|
+
#endif /* Ruby <3.1 */
|
36
|
+
|
37
|
+
/*
|
38
|
+
* :nodoc:
|
39
|
+
* returns IO object if EPOLLEXCLUSIVE works and arms readers
|
40
|
+
*/
|
41
|
+
static VALUE prep_readers(VALUE cls, VALUE readers)
|
42
|
+
{
|
43
|
+
long i;
|
44
|
+
int epfd = epoll_create1(EPOLL_CLOEXEC);
|
45
|
+
VALUE epio;
|
46
|
+
|
47
|
+
if (epfd < 0) rb_sys_fail("epoll_create1");
|
48
|
+
|
49
|
+
epio = rb_funcall(cls, rb_intern("for_fd"), 1, INT2NUM(epfd));
|
50
|
+
|
51
|
+
Check_Type(readers, T_ARRAY);
|
52
|
+
for (i = 0; i < RARRAY_LEN(readers); i++) {
|
53
|
+
int rc, fd;
|
54
|
+
struct epoll_event e;
|
55
|
+
VALUE io = rb_ary_entry(readers, i);
|
56
|
+
|
57
|
+
e.data.u64 = i; /* the reason readers shouldn't change */
|
58
|
+
|
59
|
+
/*
|
60
|
+
* I wanted to use EPOLLET here, but maintaining our own
|
61
|
+
* equivalent of ep->rdllist in Ruby-space doesn't fit
|
62
|
+
* our design at all (and the kernel already has it's own
|
63
|
+
* code path for doing it). So let the kernel spend
|
64
|
+
* cycles on maintaining level-triggering.
|
65
|
+
*/
|
66
|
+
e.events = EPOLLEXCLUSIVE | EPOLLIN;
|
67
|
+
fd = my_fileno(rb_io_get_io(io));
|
68
|
+
rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &e);
|
69
|
+
if (rc < 0) rb_sys_fail("epoll_ctl");
|
70
|
+
}
|
71
|
+
return epio;
|
72
|
+
}
|
73
|
+
#endif /* USE_EPOLL */
|
74
|
+
|
75
|
+
#if USE_EPOLL
|
76
|
+
struct ep_wait {
|
77
|
+
struct epoll_event event;
|
78
|
+
int epfd;
|
79
|
+
int timeout_msec;
|
80
|
+
};
|
81
|
+
|
82
|
+
static void *do_wait(void *ptr) /* runs w/o GVL */
|
83
|
+
{
|
84
|
+
struct ep_wait *epw = ptr;
|
85
|
+
/*
|
86
|
+
* Linux delivers epoll events in the order received, and using
|
87
|
+
* maxevents=1 ensures we pluck one item off ep->rdllist
|
88
|
+
* at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite
|
89
|
+
* easy-to-understand for anybody familiar with Ruby C).
|
90
|
+
*/
|
91
|
+
return (void *)(long)epoll_wait(epw->epfd, &epw->event, 1,
|
92
|
+
epw->timeout_msec);
|
93
|
+
}
|
94
|
+
|
95
|
+
/* :nodoc: */
|
96
|
+
/* readers must not change between prepare_readers and get_readers */
|
97
|
+
static VALUE
|
98
|
+
get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec)
|
99
|
+
{
|
100
|
+
struct ep_wait epw;
|
101
|
+
long n;
|
102
|
+
|
103
|
+
Check_Type(ready, T_ARRAY);
|
104
|
+
Check_Type(readers, T_ARRAY);
|
105
|
+
|
106
|
+
epw.epfd = my_fileno(epio);
|
107
|
+
epw.timeout_msec = NUM2INT(timeout_msec);
|
108
|
+
n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL);
|
109
|
+
if (n < 0) {
|
110
|
+
if (errno != EINTR) rb_sys_fail("epoll_wait");
|
111
|
+
} else if (n > 0) { /* maxevents is hardcoded to 1 */
|
112
|
+
VALUE obj = rb_ary_entry(readers, epw.event.data.u64);
|
113
|
+
|
114
|
+
if (RTEST(obj))
|
115
|
+
rb_ary_push(ready, obj);
|
116
|
+
} /* n == 0 : timeout */
|
117
|
+
return Qfalse;
|
118
|
+
}
|
119
|
+
#endif /* USE_EPOLL */
|
120
|
+
|
121
|
+
static void init_epollexclusive(VALUE mUnicorn)
|
122
|
+
{
|
123
|
+
#if USE_EPOLL
|
124
|
+
VALUE cWaiter = rb_define_class_under(mUnicorn, "Waiter", rb_cIO);
|
125
|
+
rb_define_singleton_method(cWaiter, "prep_readers", prep_readers, 1);
|
126
|
+
rb_define_method(cWaiter, "get_readers", get_readers, 3);
|
127
|
+
#endif
|
128
|
+
}
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#ifndef ext_help_h
|
2
|
+
#define ext_help_h
|
3
|
+
|
4
|
+
/* not all Ruby implementations support frozen objects (Rubinius does not) */
|
5
|
+
#if defined(OBJ_FROZEN)
|
6
|
+
# define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object")
|
7
|
+
#else
|
8
|
+
# define assert_frozen(f) do {} while (0)
|
9
|
+
#endif /* !defined(OBJ_FROZEN) */
|
10
|
+
|
11
|
+
static inline int str_cstr_eq(VALUE val, const char *ptr, long len)
|
12
|
+
{
|
13
|
+
return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len));
|
14
|
+
}
|
15
|
+
|
16
|
+
#define STR_CSTR_EQ(val, const_str) \
|
17
|
+
str_cstr_eq(val, const_str, sizeof(const_str) - 1)
|
18
|
+
|
19
|
+
/* strcasecmp isn't locale independent */
|
20
|
+
static int str_cstr_case_eq(VALUE val, const char *ptr, long len)
|
21
|
+
{
|
22
|
+
if (RSTRING_LEN(val) == len) {
|
23
|
+
const char *v = RSTRING_PTR(val);
|
24
|
+
|
25
|
+
for (; len--; ++ptr, ++v) {
|
26
|
+
if ((*ptr == *v) || (*v >= 'A' && *v <= 'Z' && (*v | 0x20) == *ptr))
|
27
|
+
continue;
|
28
|
+
return 0;
|
29
|
+
}
|
30
|
+
return 1;
|
31
|
+
}
|
32
|
+
return 0;
|
33
|
+
}
|
34
|
+
|
35
|
+
#define STR_CSTR_CASE_EQ(val, const_str) \
|
36
|
+
str_cstr_case_eq(val, const_str, sizeof(const_str) - 1)
|
37
|
+
|
38
|
+
#endif /* ext_help_h */
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# -*- encoding: binary -*-
|
2
|
+
# frozen_string_literal: false
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
have_func("rb_hash_clear", "ruby.h") or abort 'Ruby 2.0+ required'
|
6
|
+
|
7
|
+
message('checking if String#-@ (str_uminus) dedupes... ')
|
8
|
+
begin
|
9
|
+
a = -(%w(t e s t).join)
|
10
|
+
b = -(%w(t e s t).join)
|
11
|
+
if a.equal?(b)
|
12
|
+
$CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=1 '
|
13
|
+
message("yes\n")
|
14
|
+
else
|
15
|
+
$CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
|
16
|
+
message("no, needs Ruby 2.5+\n")
|
17
|
+
end
|
18
|
+
rescue NoMethodError
|
19
|
+
$CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 '
|
20
|
+
message("no, String#-@ not available\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
message('checking if Hash#[]= (rb_hash_aset) dedupes... ')
|
24
|
+
h = {}
|
25
|
+
x = {}
|
26
|
+
r = rand.to_s
|
27
|
+
h[%W(#{r}).join('')] = :foo
|
28
|
+
x[%W(#{r}).join('')] = :foo
|
29
|
+
if x.keys[0].equal?(h.keys[0])
|
30
|
+
$CPPFLAGS += ' -DHASH_ASET_DEDUPE=1 '
|
31
|
+
message("yes\n")
|
32
|
+
else
|
33
|
+
$CPPFLAGS += ' -DHASH_ASET_DEDUPE=0 '
|
34
|
+
message("no, needs Ruby 2.6+\n")
|
35
|
+
end
|
36
|
+
|
37
|
+
if have_func('epoll_create1', %w(sys/epoll.h))
|
38
|
+
have_func('rb_io_descriptor') # Ruby 3.1+
|
39
|
+
end
|
40
|
+
create_makefile("unicorn_http")
|
@@ -0,0 +1,97 @@
|
|
1
|
+
#ifndef global_variables_h
|
2
|
+
#define global_variables_h
|
3
|
+
static VALUE eHttpParserError;
|
4
|
+
static VALUE e413;
|
5
|
+
static VALUE e414;
|
6
|
+
|
7
|
+
static VALUE g_rack_url_scheme;
|
8
|
+
static VALUE g_request_method;
|
9
|
+
static VALUE g_request_uri;
|
10
|
+
static VALUE g_fragment;
|
11
|
+
static VALUE g_query_string;
|
12
|
+
static VALUE g_http_version;
|
13
|
+
static VALUE g_request_path;
|
14
|
+
static VALUE g_path_info;
|
15
|
+
static VALUE g_server_name;
|
16
|
+
static VALUE g_server_port;
|
17
|
+
static VALUE g_server_protocol;
|
18
|
+
static VALUE g_http_host;
|
19
|
+
static VALUE g_http_x_forwarded_proto;
|
20
|
+
static VALUE g_http_x_forwarded_ssl;
|
21
|
+
static VALUE g_http_transfer_encoding;
|
22
|
+
static VALUE g_content_length;
|
23
|
+
static VALUE g_http_trailer;
|
24
|
+
static VALUE g_http_connection;
|
25
|
+
static VALUE g_port_80;
|
26
|
+
static VALUE g_port_443;
|
27
|
+
static VALUE g_localhost;
|
28
|
+
static VALUE g_http;
|
29
|
+
static VALUE g_https;
|
30
|
+
static VALUE g_http_09;
|
31
|
+
static VALUE g_http_10;
|
32
|
+
static VALUE g_http_11;
|
33
|
+
|
34
|
+
/** Defines common length and error messages for input length validation. */
|
35
|
+
#define DEF_MAX_LENGTH(N, length) \
|
36
|
+
static const size_t MAX_##N##_LENGTH = length; \
|
37
|
+
static const char * const MAX_##N##_LENGTH_ERR = \
|
38
|
+
"HTTP element " # N " is longer than the " # length " allowed length."
|
39
|
+
|
40
|
+
NORETURN(static void parser_raise(VALUE klass, const char *));
|
41
|
+
|
42
|
+
/**
|
43
|
+
* Validates the max length of given input and throws an HttpParserError
|
44
|
+
* exception if over.
|
45
|
+
*/
|
46
|
+
#define VALIDATE_MAX_LENGTH(len, N) do { \
|
47
|
+
if (len > MAX_##N##_LENGTH) \
|
48
|
+
parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \
|
49
|
+
} while (0)
|
50
|
+
|
51
|
+
#define VALIDATE_MAX_URI_LENGTH(len, N) do { \
|
52
|
+
if (len > MAX_##N##_LENGTH) \
|
53
|
+
parser_raise(e414, MAX_##N##_LENGTH_ERR); \
|
54
|
+
} while (0)
|
55
|
+
|
56
|
+
/** Defines global strings in the init method. */
|
57
|
+
#define DEF_GLOBAL(N, val) do { \
|
58
|
+
g_##N = str_new_dd_freeze(val, (long)sizeof(val) - 1); \
|
59
|
+
rb_gc_register_mark_object(g_##N); \
|
60
|
+
} while (0)
|
61
|
+
|
62
|
+
/* Defines the maximum allowed lengths for various input elements.*/
|
63
|
+
DEF_MAX_LENGTH(FIELD_NAME, 256);
|
64
|
+
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
65
|
+
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15);
|
66
|
+
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
67
|
+
DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */
|
68
|
+
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
69
|
+
|
70
|
+
static void init_globals(void)
|
71
|
+
{
|
72
|
+
DEF_GLOBAL(rack_url_scheme, "rack.url_scheme");
|
73
|
+
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
74
|
+
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
75
|
+
DEF_GLOBAL(fragment, "FRAGMENT");
|
76
|
+
DEF_GLOBAL(query_string, "QUERY_STRING");
|
77
|
+
DEF_GLOBAL(http_version, "HTTP_VERSION");
|
78
|
+
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
79
|
+
DEF_GLOBAL(path_info, "PATH_INFO");
|
80
|
+
DEF_GLOBAL(server_name, "SERVER_NAME");
|
81
|
+
DEF_GLOBAL(server_port, "SERVER_PORT");
|
82
|
+
DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
|
83
|
+
DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO");
|
84
|
+
DEF_GLOBAL(http_x_forwarded_ssl, "HTTP_X_FORWARDED_SSL");
|
85
|
+
DEF_GLOBAL(port_80, "80");
|
86
|
+
DEF_GLOBAL(port_443, "443");
|
87
|
+
DEF_GLOBAL(localhost, "localhost");
|
88
|
+
DEF_GLOBAL(http, "http");
|
89
|
+
DEF_GLOBAL(https, "https");
|
90
|
+
DEF_GLOBAL(http_11, "HTTP/1.1");
|
91
|
+
DEF_GLOBAL(http_10, "HTTP/1.0");
|
92
|
+
DEF_GLOBAL(http_09, "HTTP/0.9");
|
93
|
+
}
|
94
|
+
|
95
|
+
#undef DEF_GLOBAL
|
96
|
+
|
97
|
+
#endif /* global_variables_h */
|
@@ -0,0 +1,91 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <time.h>
|
3
|
+
#include <sys/time.h>
|
4
|
+
#include <stdio.h>
|
5
|
+
|
6
|
+
static const size_t buf_capa = sizeof("Thu, 01 Jan 1970 00:00:00 GMT");
|
7
|
+
static VALUE buf;
|
8
|
+
static char *buf_ptr;
|
9
|
+
static const char week[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
|
10
|
+
static const char months[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
|
11
|
+
"Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
|
12
|
+
|
13
|
+
/* for people on wonky systems only */
|
14
|
+
#ifndef HAVE_GMTIME_R
|
15
|
+
# warning using fake gmtime_r
|
16
|
+
static struct tm * my_gmtime_r(time_t *now, struct tm *tm)
|
17
|
+
{
|
18
|
+
struct tm *global = gmtime(now);
|
19
|
+
if (global)
|
20
|
+
*tm = *global;
|
21
|
+
return tm;
|
22
|
+
}
|
23
|
+
# define gmtime_r my_gmtime_r
|
24
|
+
#endif
|
25
|
+
|
26
|
+
|
27
|
+
/*
|
28
|
+
* Returns a string which represents the time as rfc1123-date of HTTP-date
|
29
|
+
* defined by RFC 2616:
|
30
|
+
*
|
31
|
+
* day-of-week, DD month-name CCYY hh:mm:ss GMT
|
32
|
+
*
|
33
|
+
* Note that the result is always GMT.
|
34
|
+
*
|
35
|
+
* This method is identical to Time#httpdate in the Ruby standard library,
|
36
|
+
* except it is implemented in C for performance. We always saw
|
37
|
+
* Time#httpdate at or near the top of the profiler output so we
|
38
|
+
* decided to rewrite this in C.
|
39
|
+
*
|
40
|
+
* Caveats: it relies on a Ruby implementation with the global VM lock,
|
41
|
+
* a thread-safe version will be provided when a Unix-only, GVL-free Ruby
|
42
|
+
* implementation becomes viable.
|
43
|
+
*/
|
44
|
+
static VALUE httpdate(VALUE self)
|
45
|
+
{
|
46
|
+
static time_t last;
|
47
|
+
struct timeval now;
|
48
|
+
struct tm tm;
|
49
|
+
|
50
|
+
/*
|
51
|
+
* Favor gettimeofday(2) over time(2), as the latter can return the
|
52
|
+
* wrong value in the first 1 .. 2.5 ms of every second(!)
|
53
|
+
*
|
54
|
+
* https://lore.kernel.org/git/20230320230507.3932018-1-gitster@pobox.com/
|
55
|
+
* https://inbox.sourceware.org/libc-alpha/20230306160321.2942372-1-adhemerval.zanella@linaro.org/T/
|
56
|
+
* https://sourceware.org/bugzilla/show_bug.cgi?id=30200
|
57
|
+
*/
|
58
|
+
if (gettimeofday(&now, NULL))
|
59
|
+
rb_sys_fail("gettimeofday");
|
60
|
+
|
61
|
+
if (last == now.tv_sec)
|
62
|
+
return buf;
|
63
|
+
last = now.tv_sec;
|
64
|
+
gmtime_r(&now.tv_sec, &tm);
|
65
|
+
|
66
|
+
/* we can make this thread-safe later if our Ruby loses the GVL */
|
67
|
+
snprintf(buf_ptr, buf_capa,
|
68
|
+
"%s, %02d %s %4d %02d:%02d:%02d GMT",
|
69
|
+
week + (tm.tm_wday * 4),
|
70
|
+
tm.tm_mday,
|
71
|
+
months + (tm.tm_mon * 4),
|
72
|
+
tm.tm_year + 1900,
|
73
|
+
tm.tm_hour,
|
74
|
+
tm.tm_min,
|
75
|
+
tm.tm_sec);
|
76
|
+
|
77
|
+
return buf;
|
78
|
+
}
|
79
|
+
|
80
|
+
void init_unicorn_httpdate(void)
|
81
|
+
{
|
82
|
+
VALUE mod = rb_define_module("Unicorn");
|
83
|
+
mod = rb_define_module_under(mod, "HttpResponse");
|
84
|
+
|
85
|
+
buf = rb_str_new(0, buf_capa - 1);
|
86
|
+
rb_gc_register_mark_object(buf);
|
87
|
+
buf_ptr = RSTRING_PTR(buf);
|
88
|
+
httpdate(Qnil);
|
89
|
+
|
90
|
+
rb_define_method(mod, "httpdate", httpdate, 0);
|
91
|
+
}
|