The stock mod_rewrite interface with an external rewrite engine via stdin/stdout requires a lockfile to prevent multiple child processes screwing up each other's rewriting. This essentially means that all rewrite processes are serialised, and can result in a serious queuing of requests if the rewrite program encounters any delay. The following patch will enable mod_rewrite to communicate with the external rewrite engine over TCP/IP sockets, meaning that a multi-threaded (or multi-process, you choose) rewrite engine can deal with several rewrite requests at once, without the need to serialise the requests. I'm not suggesting for one minute that it's perfect, or even close, but it's a means to an end and it actually works. There's a slight change to the RewriteMap directive: RewriteMap name "prg:/path/to/prog args" port Where port is the port to send rewrite requests to. I'm sure this could be done more elegantly by having an extra RewriteMap type, but tying it into the prg: type does have the advantage of ensuring that the process is up and running at the same time Apache is. --- src/modules/standard/mod_rewrite.c Tue May 7 16:34:43 2002 +++ /home/jtait/src/apache_1.3.24/src/modules/standard/mod_rewrite.c Tue May 7 16:14:34 2002 @@ -168,7 +168,7 @@ "an input string and a to be applied regexp-pattern" }, { "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS, "an URL-applied regexp-pattern and a substitution URL" }, - { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE2, + { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE23, "a mapname and a filename" }, { "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, TAKE1, "the filename of a lockfile used for inter-process synchronization"}, @@ -448,7 +448,7 @@ } static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, char *a1, - char *a2) + char *a2, char *a3) { rewrite_server_conf *sconf; rewritemap_entry *new; @@ -485,6 +485,12 @@ new->type = MAPTYPE_PRG; new->datafile = a2+4; new->checkfile = a2+4; + if (a3 == NULL) { + new->port = 0; + } + else { + new->port = atoi(a3); + } } else if (strncmp(a2, "int:", 4) == 0) { new->type = MAPTYPE_INT; @@ -2753,7 +2759,7 @@ } else if (s->type == MAPTYPE_PRG) { if ((value = - lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) { + lookup_map_program(r, s->fpin, s->fpout, key, s->port)) != NULL) { rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s", s->name, key, value); return value; @@ -2891,11 +2897,12 @@ } #endif -static char *lookup_map_program(request_rec *r, int fpin, int fpout, char *key) +static char *lookup_map_program(request_rec *r, int fpin, int fpout, char *key, unsigned short port) { char buf[LONG_STRING_LEN]; char c; int i; + int sock; #ifndef NO_WRITEV struct iovec iov[2]; #endif @@ -2909,33 +2916,71 @@ return NULL; } - /* take the lock */ - rewritelock_alloc(r); + if (port != 0) { + /* create a socket connection to the external program */ + sock = make_socket_connection(r, port); + if (sock == -1) { + return NULL; + } + + ap_hard_timeout("Send external rewrite request", r); + /* write out the request key */ + i = send(sock, key, strlen(key), 0); + if (i != strlen(key)) { + ap_kill_timeout(r); + return NULL; + } + ap_reset_timeout(r); + + i = send(sock, "\n", 1, 0); + if (i != 1) { + ap_kill_timeout(r); + return NULL; + } + ap_kill_timeout(r); + + /* read in the response value */ + ap_hard_timeout("Receive external rewrite response", r); + i = 0; + while (recv(sock, &c, 1, 0) == 1 && (i < LONG_STRING_LEN -1)) { + if (c == '\n') { + break; + } + buf[i++] = c; + } + buf[i] = '\0'; + ap_pclosesocket(r->pool, sock); + ap_kill_timeout(r); + } + else { + /* take the lock */ + rewritelock_alloc(r); - /* write out the request key */ + /* write out the request key */ #ifdef NO_WRITEV - write(fpin, key, strlen(key)); - write(fpin, "\n", 1); + write(fpin, key, strlen(key)); + write(fpin, "\n", 1); #else - iov[0].iov_base = key; - iov[0].iov_len = strlen(key); - iov[1].iov_base = "\n"; - iov[1].iov_len = 1; - writev(fpin, iov, 2); + iov[0].iov_base = key; + iov[0].iov_len = strlen(key); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; + writev(fpin, iov, 2); #endif - /* read in the response value */ - i = 0; - while (read(fpout, &c, 1) == 1 && (i < LONG_STRING_LEN-1)) { - if (c == '\n') { - break; + /* read in the response value */ + i=0; + while (read(fpout, &c, 1) == 1 && (i < LONG_STRING_LEN-1)) { + if (c == '\n') { + break; + } + buf[i++] = c; } - buf[i++] = c; - } - buf[i] = '\0'; + buf[i] = '\0'; - /* give the lock back */ - rewritelock_free(r); + /* give the lock back */ + rewritelock_free(r); + } if (strcasecmp(buf, "NULL") == 0) { return NULL; @@ -2945,6 +2990,45 @@ } } +static int make_socket_connection(request_rec *r, unsigned short port) +{ + struct hostent *server_hp; + struct sockaddr_in addr; + int sock; + int i; + + memset(&addr, '\0', sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + server_hp = gethostbyname("localhost"); + addr.sin_addr.s_addr = ((struct in_addr *)server_hp->h_addr_list[0])->s_addr; + + sock = ap_psocket(r->pool, PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Unable to create socket"); + return -1; + } + + ap_hard_timeout("External rewriter connect", r); + do { + i = connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)); +#if defined(WIN32) || defined(NETWARE) + if (i == SOCKET_ERROR) { + errno = WSAGetLastError(); + } +#endif /* WIN32 */ + } while (i == -1 && errno == EINTR); + if (i == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Unable to connect to external rewriter on %s:%d", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + sock = -1; + } + ap_kill_timeout(r); + return sock; +} + static char *lookup_map_internal(request_rec *r, char *(*func)(request_rec *, char *), char *key) --- src/modules/standard/mod_rewrite.h Tue May 7 16:34:43 2002 +++ /home/jtait/src/apache_1.3.24/src/modules/standard/mod_rewrite.h Fri May 3 17:18:39 2002 @@ -274,6 +274,7 @@ int fperr; /* err file pointer for program maps */ char *(*func)(request_rec *, /* function pointer for internal maps */ char *); + unsigned short port; /* port number for program maps */ } rewritemap_entry; typedef struct { @@ -383,7 +384,7 @@ static const char *cmd_rewritelog (cmd_parms *cmd, void *dconf, char *a1); static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1); static const char *cmd_rewritemap (cmd_parms *cmd, void *dconf, char *a1, - char *a2); + char *a2, char *a3); static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1); static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf, char *a1); @@ -440,7 +441,8 @@ static char *lookup_map_dbmfile(request_rec *r, char *file, char *key); #endif static char *lookup_map_program(request_rec *r, int fpin, - int fpout, char *key); + int fpout, char *key, unsigned short port); +static int make_socket_connection(request_rec *r, unsigned short port); static char *lookup_map_internal(request_rec *r, char *(*func)(request_rec *r, char *key), char *key);
OK, I've spent some time coming up with a better solution. The following patch is much more elegant, and allows Apache to talk to another server altogether (what TCP/IP was designed for, right?). It doesn't ensure that the external rewrite engine is started, but it's a small price to pay for a potentially large increase in performance. The new format is: RewriteMap tcp:ipaddr.or.dnsname.of.host:port The port does not have a default value, so it must be specified. --- src/modules/standard/mod_rewrite.c Sat Mar 16 23:44:20 2002 +++ /home/jtait/src/apache_1.3.24/src/modules/standard/mod_rewrite.c Wed May 8 15:18:36 2002 @@ -507,6 +507,11 @@ a2+4, NULL); } } + else if (strncmp(a2, "tcp:", 4) == 0) { + new->type = MAPTYPE_TCP; + new->datafile = a2+4; + new->checkfile = NULL; + } else { new->type = MAPTYPE_TXT; new->datafile = a2; @@ -2751,6 +2756,17 @@ return NULL; #endif } + else if (s->type == MAPTYPE_TCP) { + if ((value = lookup_map_tcp(r, s->datafile, key)) != NULL) { + rewritelog(r, 5, "map lookup OK: map=%s(%s) key=%s -> val=%s", + s->name, s->datafile, key, value); + return value; + } + else { + rewritelog(r, 5, "map lookup FAILED: map=%s(%s), key=%s", + s->name, s->datafile, key); + } + } else if (s->type == MAPTYPE_PRG) { if ((value = lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) { @@ -2945,6 +2961,126 @@ } } +static char *lookup_map_tcp(request_rec *r, const char *hoststr, char *key) +{ + char buf[LONG_STRING_LEN]; + char c; + int i, sock; + + /* create a socket connection to the external program */ + sock = make_socket_connection(r, hoststr); + if (sock == -1) { + return NULL; + } + + ap_hard_timeout("Send external rewrite request", r); + /* write out the request key */ + i = send(sock, key, strlen(key), 0); + if (i != strlen(key)) { + ap_kill_timeout(r); + return NULL; + } + ap_reset_timeout(r); + + i = send(sock, "\n", 1, 0); + if (i != 1) { + ap_kill_timeout(r); + return NULL; + } + ap_kill_timeout(r); + + /* read in the response value */ + ap_hard_timeout("Receive external rewrite response", r); + i = 0; + while (recv(sock, &c, 1, 0) == 1 && (i < LONG_STRING_LEN - 1)) { + if (c == '\n') { + break; + } + buf[i++] = c; + } + buf[i] = '\0'; + ap_pclosesocket(r->pool, sock); + ap_kill_timeout(r); + + if (strcasecmp(buf, "NULL") == 0) { + return NULL; + } + else { + return ap_pstrdup(r->pool, buf); + } +} + +static int make_socket_connection(request_rec *r, const char *hoststr) +{ + struct hostent *server_hp; + struct sockaddr_in addr; + int i, sock, port; + char *host; + char *portstr; + + memset(&addr, '\0', sizeof(addr)); + addr.sin_family = AF_INET; + + host = ap_pstrdup(r->pool, hoststr); + rewritelog(r, 5, "Extracting port from string: [%s]", host); + + port = 0; + portstr = strchr(host, ':'); + if (portstr != NULL) { + *(portstr++) = '\0'; + rewritelog(r, 5, "Port found: [%s]", portstr); + if (ap_isdigit(*portstr)) { + port = atoi(portstr); + } + } + + addr.sin_port = htons(port); + + for (i = 0; host[i] != '\0'; i++) { + if(!ap_isdigit(host[i]) && host[i] != '.') { + break; + } + } + if (host[i] == '\0') { + rewritelog(r, 3, "External rewrite host IP: %s:%d", host, port); + addr.sin_addr.s_addr = inet_addr(host); + } + else { + rewritelog(r, 3, "External rewrite hostname: %s:%d", host, port); + server_hp = gethostbyname(host); + if (host == NULL) { + addr.sin_addr.s_addr = 0; + } + else { + addr.sin_addr.s_addr = ((struct in_addr *)server_hp->h_addr_list[0])->s_addr; + } + } + + sock = ap_psocket(r->pool, PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, "Unable to create socket"); + return -1; + } + + ap_hard_timeout("External rewriter connect", r); + do { + i = connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)); +#if defined(WIN32) || defined(NETWARE) + if (i == SOCKET_ERROR) { + errno = WSAGetLastError(); + } +#endif /* WIN32 */ + } while (i == -1 && errno == EINTR); + if (i == -1) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, r, + "Unable to connect to external rewriter on %s:%d", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + sock = -1; + } + ap_kill_timeout(r); + return sock; +} + static char *lookup_map_internal(request_rec *r, char *(*func)(request_rec *, char *), char *key) --- src/modules/standard/mod_rewrite.h Wed Mar 13 21:05:34 2002 +++ /home/jtait/src/apache_1.3.24/src/modules/standard/mod_rewrite.h Wed May 8 15:00:22 2002 @@ -220,6 +220,7 @@ #define MAPTYPE_PRG 1<<2 #define MAPTYPE_INT 1<<3 #define MAPTYPE_RND 1<<4 +#define MAPTYPE_TCP 1<<5 #define ENGINE_DISABLED 1<<0 #define ENGINE_ENABLED 1<<1 @@ -441,6 +442,8 @@ #endif static char *lookup_map_program(request_rec *r, int fpin, int fpout, char *key); +static char *lookup_map_tcp(request_rec *r, const char *hoststr, char *key); +static int make_socket_connection(request_rec *r, const char *hoststr); static char *lookup_map_internal(request_rec *r, char *(*func)(request_rec *r, char *key), char *key);
Created attachment 1812 [details] Patch to allow mod_rewrite to talk to an external rewrite engine over TCP/IP
I'm going through the bug db to make sure patches are findable. Please see http://httpd.apache.org/dev/patches.html