From 8f3ac0d2260dd9b5955ccc1781a830ea8f1a7fbc Mon Sep 17 00:00:00 2001 From: ferg Date: Thu, 13 Oct 2011 00:35:09 +0000 Subject: [PATCH] memcache server functions git-svn-id: svn://svn.caucho.com/resin/trunk@8487 9c94448d-38f1-0310-a231-d98308ff1ebf --- README | 131 +++--- .../distcache/memcache/MemcacheClient.java | 12 +- .../memcache/MemcacheConnection.java | 430 +++++++++++++++++- resin-doc/admin/resin-admin-console.xtp | 41 +- resin-doc/admin/toc.xml | 3 +- resin-doc/changes/changes.xtp | 1 + 6 files changed, 522 insertions(+), 96 deletions(-) diff --git a/README b/README index 56426b964..8b56a0dc1 100644 --- a/README +++ b/README @@ -6,8 +6,10 @@ This is the README file for Resin 4.0(tm) The documentation included with Resin is in XTP format starting at http://localhost:8080/resin-doc/index.xtp. -The full documentation is also available online at -http://www.caucho.com/resin-4.0/ if you have trouble getting started. +For more information about configuring Resin, the full documentation is +also available online at: + + http://www.caucho.com/resin-4.0/ Questions should go to our mailing list or forums. @@ -15,6 +17,7 @@ Questions should go to our mailing list or forums. http://forum.caucho.com -- forum http://bugs.caucho.com -- bug reports + I) Licensing/Support -------------------- @@ -29,18 +32,21 @@ technical support options to fit your support and budget needs. http://www.caucho.com/resin/sales -- licenses and support + II) Preconditions ----------------- -1) Resin needs a Java JDK before it can run. Resin 4.0 works with -JDK 1.6 or greater. + 1) Resin needs a Java JDK before it can run. Resin 4.0 works with + JDK 1.6 or greater. -Some locations for JDKs include: + Some locations for JDKs include: -Oracle: - http://www.oracle.com/technetwork/java/javase/downloads/index.html + Oracle: + http://www.oracle.com/technetwork/java/javase/downloads/index.html -2) On Windows, you'll need an unzip tool available at http://www.winzip.com + 2) On Windows, you'll need an unzip tool available at http://www.winzip.com + + 3) A HTML 5 browser is needed for some features of the /resin-admin tool. III) Getting Started -------------------- @@ -49,67 +55,90 @@ The included HTTP/1.1 server makes it easy to evaluate Resin, JSP and servlets. Just start it up. The default configuration file is found in resin-4.0.x/conf/resin.xml. -1) On Unix, you'll want to build the JNI libraries: + 1) On Unix, you'll want to build the JNI libraries: + + resin-4.0.x> ./configure --prefix=`pwd` + resin-4.0.x> make + resin-4.0.x> make install + + 2) To start the server: + + unix> bin/resin.sh start + + or + + C:\> java -jar lib/resin.jar start + + The server listens to port 8080. Port 8080 is the default specified + in the configuration file resin-4.0.x/conf/resin.xml + + Log files are in resin-4.0.x/log + + 3) Usually, Resin can find the JDK, but you may need to set JAVA_HOME in some + configurations. + + Starting Resin with -verbose is a great way to see what environment + Resin is running. - resin-4.0.x> ./configure --prefix=`pwd` - resin-4.0.x> make - resin-4.0.x> make install + 4) Browse http://localhost:8080 or equivalently http://127.0.0.1:8080. -2) To start the server: + 5) Go through the documentation at http://localhost:8080/resin-doc - unix> java -jar lib/resin.jar start - or +IV) Application Deployment +-------------------------- - C:\> java -jar lib/resin.jar start + 1) You can use the command-line to deploy a .war file: + + unix> bin/resin.sh deploy foo.war - The server listens to port 8080. Port 8080 is the default specified - in the configuration file resin-4.0.x/conf/resin.xml + 2) You can also copy the war file to the webapps directory. -3) Usually, Resin can find the JDK, but you may need to set JAVA_HOME in some - configurations. + To deploy a war file, place the file in resin-4.0.x/webapps/foo.war + Resin will expand the war file, and the application will be available + with the url http://localhost:8080/foo/ - Starting Resin with -verbose is a great way to see what environment - Resin is running. + 3) Resin supports the development of applications without requiring a war + file deployment. Create a directory resin-4.0.x/webapps/bar to + correspond to url http://localhost:8080/bar/. Java source files + placed in resin-4.0.x/webapps/bar/WEB-INF/classes/ are automatically + compiled by Resin. -4) Browse http://localhost:8080 or equivalently http://127.0.0.1:8080. + resin-4.0.x/webapps/bar/index.jsp + --> http://localhost:8080/bar/ + --> http://localhost:8080/bar/index.jsp -5) Go through the documentation at http://localhost:8080/resin-doc + resin-4.0.x/webapps/bar/WEB-INF/web.xml + -- servlet/jsp configuration file -6) For JSP, create test.jsp in resin-4.0.x/webapps/ROOT/test.jsp and browse - http://localhost:8080/test.jsp. + resin-4.0.x/webapps/bar/WEB-INF/resin-web.xml + -- Resin specific configuration - Resin serves files from resin-4.0.x/webapps/ROOT/ in the default - configuration. All files with the extension '.jsp' are interpreted - as jsp files. + resin-4.0.x/webapps/bar/WEB-INF/classes/example/HelloServlet.java + -- java source file, automatically compiled by Resin + + 4) For PHP, create test.php in resin-4.0.x/webapps/ROOT/test.php and browse + http://localhost:8080/test.php. -7) For PHP, create test.php in resin-4.0.x/webapps/ROOT/test.php and browse - http://localhost:8080/test.php. -8) To deploy a war file, place the file in resin-4.0.x/webapps/foo.war - Resin will expand the war file, and the application will be available - with the url http://localhost:8080/foo/ +IV) PDF Reports +--------------- - Resin supports the development of applications without requiring a war - file deployment. Create a directory resin-4.0.x/webapps/bar to - correspond to url http://localhost:8080/bar/. Java source files - placed in resin-4.0.x/webapps/bar/WEB-INF/classes/ are automatically - compiled by Resin. + With Resin-Pro, you can get a PDF snapshot report of the server with the + command-line: - resin-4.0.x/webapps/bar/index.jsp - --> http://localhost:8080/bar/ - --> http://localhost:8080/bar/index.jsp + unix> bin/resin.sh pdf-report - resin-4.0.x/webapps/bar/WEB-INF/web.xml - -- servlet/jsp configuration file + You can also get a watchdog restart report with the -watchdog option + + unix> bin/resin.sh pdf-report -watchdog - resin-4.0.x/webapps/bar/WEB-INF/resin-web.xml - -- Resin specific configuration + +V) /resin-admin browser-based administration +-------------------------------------------- - resin-4.0.x/webapps/bar/WEB-INF/classes/example/HelloServlet.java - -- java source file, automatically compiled by Resin + An administration application is available at -9) An administration application is available at - http://localhost:8080/resin-admin - Instructions are provided for setting a password. + http://localhost:8080/resin-admin + Instructions are provided for setting a password. diff --git a/modules/resin/src/com/caucho/distcache/memcache/MemcacheClient.java b/modules/resin/src/com/caucho/distcache/memcache/MemcacheClient.java index a86fa1a70..cc745304c 100644 --- a/modules/resin/src/com/caucho/distcache/memcache/MemcacheClient.java +++ b/modules/resin/src/com/caucho/distcache/memcache/MemcacheClient.java @@ -341,9 +341,9 @@ public void put(Object key, Object value) throws CacheException WriteStream out = client.getOutputStream(); ReadStream is = client.getInputStream(); - //TempStream ts = serialize(value); - // long length = ts.getLength(); - long length = ((String) value).length(); + TempStream ts = serialize(value); + long length = ts.getLength(); + // long length = ((String) value).length(); out.print("set "); out.print(key); @@ -358,10 +358,10 @@ public void put(Object key, Object value) throws CacheException out.print(length); out.print("\r\n"); - out.print(value); - // ts.writeToStream(out); + //out.print(value); + ts.writeToStream(out); - System.out.println("SET-LEN: " + length); + // System.out.println("SET-LEN: " + length); out.print("\r\n"); out.flush(); diff --git a/modules/resin/src/com/caucho/distcache/memcache/MemcacheConnection.java b/modules/resin/src/com/caucho/distcache/memcache/MemcacheConnection.java index 5a515615d..5af5af786 100644 --- a/modules/resin/src/com/caucho/distcache/memcache/MemcacheConnection.java +++ b/modules/resin/src/com/caucho/distcache/memcache/MemcacheConnection.java @@ -29,6 +29,7 @@ package com.caucho.distcache.memcache; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -42,6 +43,7 @@ import com.caucho.util.CharBuffer; import com.caucho.util.HashKey; import com.caucho.vfs.ReadStream; +import com.caucho.vfs.TempStream; import com.caucho.vfs.WriteStream; /** @@ -130,8 +132,11 @@ public boolean handleRequest() throws IOException Command command = _commandMap.get(_method); if (command == null) { - System.out.println("unknown command: '" + _method + "' " + command); - return false; + WriteStream out = getWriteStream(); + + out.print("ERROR\r\n"); + + return true; } //return command.execute(this); @@ -420,6 +425,97 @@ public boolean doCommand(MemcacheConnection conn, } } + static class AppendCommand extends StoreCommand { + @Override + public boolean doCommand(MemcacheConnection conn, + String key, + long bytes, + long timeout, + int flags) + throws IOException + { + ClusterCache cache = conn.getCache(); + + ExtCacheEntry entry = cache.getExtCacheEntry(key); + + ReadStream rs = conn.getReadStream(); + + if (entry == null || entry.isValueNull()) { + rs.skip(bytes); + + return false; + } + + TempStream ts = new TempStream(); + + WriteStream os = new WriteStream(ts); + os.setDisableClose(true); + + cache.get(key, os); + + SetInputStream setIs = conn.getSetInputStream(); + + setIs.init(rs, bytes); + + os.writeStream(setIs); + os.setDisableClose(false); + os.close(); + + cache.put(key, + ts.openRead(), + entry.getIdleTimeout(), + entry.getUserFlags()); + + return true; + } + } + + static class PrependCommand extends StoreCommand { + @Override + public boolean doCommand(MemcacheConnection conn, + String key, + long bytes, + long timeout, + int flags) + throws IOException + { + ClusterCache cache = conn.getCache(); + + ExtCacheEntry entry = cache.getExtCacheEntry(key); + + ReadStream rs = conn.getReadStream(); + + if (entry == null || entry.isValueNull()) { + rs.skip(bytes); + + return false; + } + + TempStream ts = new TempStream(); + + WriteStream os = new WriteStream(ts); + os.setDisableClose(true); + + SetInputStream setIs = conn.getSetInputStream(); + + setIs.init(rs, bytes); + + os.writeStream(setIs); + + cache.get(key, os); + + os.setDisableClose(false); + os.close(); + + cache.put(key, + ts.openRead(), + entry.getIdleTimeout(), + entry.getUserFlags()); + + return true; + } + } + static class GetCommand extends Command { @Override public boolean execute(MemcacheConnection conn) @@ -432,7 +528,7 @@ public boolean execute(MemcacheConnection conn) CharBuffer cb = new CharBuffer(); while (readKey(rs, cb)) { - getCache(out, conn.getCache(), cb.toString(), conn); + getCache(out, conn.getCache(), cb.toString(), conn, 0); } int ch = rs.read(); @@ -477,10 +573,11 @@ private boolean readKey(ReadStream rs, CharBuffer cb) return true; } - private void getCache(WriteStream out, - ClusterCache cache, - String key, - MemcacheConnection conn) + protected void getCache(WriteStream out, + ClusterCache cache, + String key, + MemcacheConnection conn, + long hash) throws IOException { ExtCacheEntry entry = cache.getExtCacheEntry(key); @@ -497,6 +594,14 @@ private void getCache(WriteStream out, return; } + HashKey valueKey = entry.getValueHashKey(); + long unique = getCasKey(valueKey); + + if (hash != 0 && hash == unique) { + // get-if-modified + return; + } + out.print("VALUE "); out.print(key); out.print(" "); @@ -505,13 +610,6 @@ private void getCache(WriteStream out, long bytes = entry.getValueLength(); out.print(" "); out.print(bytes); - - HashKey valueKey = entry.getValueHashKey(); - - long unique = 0; - - unique = getCasKey(valueKey); - out.print(" "); out.print(unique); out.print("\r\n"); @@ -525,6 +623,251 @@ private void getCache(WriteStream out, } } + static class GetIfModifiedCommand extends GetCommand { + @Override + public boolean execute(MemcacheConnection conn) + throws IOException + { + ReadStream rs = conn.getReadStream(); + WriteStream out = conn.getWriteStream(); + out.setDisableClose(true); + + CharBuffer cb = new CharBuffer(); + + int ch = 0; + + for (ch = rs.read(); ch >= 0 && ch == ' '; ch = rs.read()) { + } + + for (; ch >= 0 && ch != ' ' && ch != '\n'; ch = rs.read()) { + cb.append((char) ch); + } + + for (; ch == ' '; ch = rs.read()) { + } + + long hash = 0; + + for (; '0' <= ch && ch <= '9'; ch = rs.read()) { + hash = 10 * hash + ch - '0'; + } + + getCache(out, conn.getCache(), cb.toString(), conn, hash); + + for (; ch >= 0 && ch != '\r' && ch != '\n'; ch = rs.read()) { + } + + if (ch == '\r') { + ch = rs.read(); + if (ch != '\n') { + System.out.println("PROTOL: " + ch); + throw new IOException("PROTOCOL: " + ch); + } + } + + out.print("END\r\n"); + out.flush(); + + return true; + } + } + + static class DeleteCommand extends Command { + @Override + public boolean execute(MemcacheConnection conn) + throws IOException + { + ReadStream rs = conn.getReadStream(); + WriteStream out = conn.getWriteStream(); + out.setDisableClose(true); + + boolean isNoReply = false; + + CharBuffer cb = new CharBuffer(); + + int ch = 0; + + for (ch = rs.read(); ch >= 0 && ch == ' '; ch = rs.read()) { + } + + for (; ch >= 0 && ch != ' ' && ch != '\n'; ch = rs.read()) { + cb.append((char) ch); + } + + String key = cb.toString(); + + for (; ch == ' '; ch = rs.read()) { + } + + long time = 0; + + for (; '0' <= ch && ch <= '9'; ch = rs.read()) { + time = 10 * time + ch - '0'; + } + + for (; ch == ' '; ch = rs.read()) { + } + + cb.clear(); + for (; ch >= 0 && ch != ' ' && ch != '\r' && ch != '\n'; ch = rs.read()) { + cb.append((char) ch); + } + + if (cb.length() > 0 && cb.matches("noreply")) + isNoReply = true; + + for (; ch >= 0 && ch != '\r' && ch != '\n'; ch = rs.read()) { + } + + if (ch == '\r') { + ch = rs.read(); + if (ch != '\n') { + System.out.println("PROTOL: " + ch); + throw new IOException("PROTOCOL: " + ch); + } + } + + if (deleteCache(conn.getCache(), time, key)) { + if (! isNoReply) + out.print("DELETED\r\n"); + } + else { + if (! isNoReply) + out.print("NOT_FOUND\r\n"); + } + + out.flush(); + + return true; + } + + protected boolean deleteCache(ClusterCache cache, + long time, + String key) + throws IOException + { + ExtCacheEntry entry = cache.getExtCacheEntry(key); + + cache.remove(key); + + return (entry != null && ! entry.isValueNull()); + } + } + + static class IncrementCommand extends Command { + @Override + public boolean execute(MemcacheConnection conn) + throws IOException + { + ReadStream rs = conn.getReadStream(); + WriteStream out = conn.getWriteStream(); + out.setDisableClose(true); + + boolean isNoReply = false; + + CharBuffer cb = new CharBuffer(); + + int ch = 0; + + for (ch = rs.read(); ch >= 0 && ch == ' '; ch = rs.read()) { + } + + for (; ch >= 0 && ch != ' ' && ch != '\n'; ch = rs.read()) { + cb.append((char) ch); + } + + String key = cb.toString(); + + for (; ch == ' '; ch = rs.read()) { + } + + long delta = 0; + + for (; '0' <= ch && ch <= '9'; ch = rs.read()) { + delta = 10 * delta + ch - '0'; + } + + for (; ch == ' '; ch = rs.read()) { + } + + cb.clear(); + for (; ch >= 0 && ch != ' ' && ch != '\r' && ch != '\n'; ch = rs.read()) { + cb.append((char) ch); + } + + if (cb.length() > 0 && cb.matches("noreply")) + isNoReply = true; + + for (; ch >= 0 && ch != '\r' && ch != '\n'; ch = rs.read()) { + } + + if (ch == '\r') { + ch = rs.read(); + if (ch != '\n') { + System.out.println("PROTOL: " + ch); + throw new IOException("PROTOCOL: " + ch); + } + } + + long value = changeCache(conn.getCache(), key, delta); + + if (isNoReply) { + + } + else if (value == Long.MIN_VALUE) { + out.print("NOT_FOUND\r\n"); + } + else { + out.print("VALUE " + value + "\r\n"); + } + + return true; + } + + protected long changeCache(ClusterCache cache, + String key, + long delta) + throws IOException + { + return incrementCache(cache, key, delta); + } + + protected long incrementCache(ClusterCache cache, + String key, + long delta) + throws IOException + { + ExtCacheEntry entry = cache.getExtCacheEntry(key); + + if (entry == null || entry.isValueNull()) + return Long.MIN_VALUE; + + CounterStream os = new CounterStream(); + + cache.get(key, os); + + long newValue = os.getValue() + delta; + + byte []values = String.valueOf(newValue).getBytes(); + + ByteArrayInputStream bis = new ByteArrayInputStream(values); + + cache.put(key, bis, entry.getIdleTimeout()); + + return newValue; + } + } + + static class DecrementCommand extends IncrementCommand { + protected long changeCache(ClusterCache cache, + String key, + long delta) + throws IOException + { + return incrementCache(cache, key, -delta); + } + } + static class QuitCommand extends Command { @Override public boolean execute(MemcacheConnection conn) @@ -556,6 +899,26 @@ public boolean execute(MemcacheConnection conn) } } + static class VerbosityCommand extends Command { + @Override + public boolean execute(MemcacheConnection conn) + throws IOException + { + ReadStream rs = conn.getReadStream(); + + int ch; + + while ((ch = rs.read()) >= 0 && ch != '\n') { + } + + WriteStream out = conn.getWriteStream(); + + out.print("OK\r\n"); + + return true; + } + } + static class StatsCommand extends Command { @Override public boolean execute(MemcacheConnection conn) @@ -584,12 +947,22 @@ public boolean execute(MemcacheConnection conn) if ("".equals(key)) { out.print("END\r\n"); } + else if ("resin".equals(key)) { + printResinStats(out); + out.print("END\r\n"); + } else { out.print("ERROR\r\n"); } return true; } + + private void printResinStats(WriteStream out) + throws IOException + { + out.print("STAT enable_get_if_modified 1\r\n"); + } } static class SetInputStream extends InputStream { @@ -660,14 +1033,43 @@ public final void write(byte []buffer, int offset, int length) } } + static class CounterStream extends OutputStream { + private int _sign = 1; + private long _value; + + public void write(int ch) + { + if (ch == '-') + _sign = -1; + + if ('0' <= ch && ch <= '9') + _value = 10 * _value + ch - '0'; + } + + public long getValue() + { + return _sign * _value; + } + + public void flush() {} + public void close() {} + } + static { addCommand("add", new AddCommand()); + addCommand("append", new AppendCommand()); addCommand("get", new GetCommand()); addCommand("gets", new GetCommand()); + addCommand("get_if_modified", new GetIfModifiedCommand()); + addCommand("decr", new DecrementCommand()); + addCommand("delete", new DeleteCommand()); + addCommand("incr", new IncrementCommand()); + addCommand("prepend", new PrependCommand()); addCommand("quit", new QuitCommand()); addCommand("replace", new ReplaceCommand()); addCommand("set", new SetCommand()); addCommand("stats", new StatsCommand()); addCommand("version", new VersionCommand()); + addCommand("verbosity", new VerbosityCommand()); } } diff --git a/resin-doc/admin/resin-admin-console.xtp b/resin-doc/admin/resin-admin-console.xtp index ed69e32a0..dbf6c67b3 100644 --- a/resin-doc/admin/resin-admin-console.xtp +++ b/resin-doc/admin/resin-admin-console.xtp @@ -192,39 +192,32 @@ a dedicated profiler available.

-

For security, you will also need to add a <user> to -the <resin:AdminAuthenticator> section of the resin.xml. -The password will be -a MD5 hash. By default, the /resin-admin web-app provides a form -for generating the hash codes. You will need to copy the generated -password into the resin.xml. This guarantees that you have access -to the resin.xml itself to add any users. In other words, the configuration -is very cautious about security issues to enable the administration.

- - +

For advanced users, you can change the standard AdminAuthenticator +to be any of the Resin authenticators with a little extra configuration. +The default authenticator uses an XML file called admin-users.xml to +define the admin users.

+ +

You can create an alternative authenticator by configuring it and +setting its CDI name to be "resinAuth". For example, the following +will configure an XmlAuthenticator.

+ + <resin xmlns="http://caucho.com/ns/resin" + xmlns:ee="urn:java:ee"> xmlns:resin="urn:java:com.caucho.resin"> - <resin:AdminAuthenticator> - <resin:import path="${__DIR__}/admin-users.xml" optional="true"/> - </resin:AdminAuthenticator> + <resin:XmlAuthenticator ee:Named="resinAuth"> + <user name="admin" password="{SSHA}h5QdSulQyqIgYo7BIJ3YfnRSY56kD847"/> + </resin:XmlAuthenticator> + ... + <cluster id=""> <host id=""> - ... - <web-app id="/resin-admin" root-directory="${resin.root}/doc/admin"> - <prologue> - <!-- only allow access from internal IP (10.*,192.168.*,127.*) --> - <resin:set var="resin_admin_external" value="false"/> - <resin:set var="resin_admin_insecure" value="true"/> - </prologue> + ... </web-app> - -</host> -</cluster> -</resin>
diff --git a/resin-doc/admin/toc.xml b/resin-doc/admin/toc.xml index 5f64a826e..558bf0f8c 100644 --- a/resin-doc/admin/toc.xml +++ b/resin-doc/admin/toc.xml @@ -3,7 +3,8 @@ + description="Getting Started with Resin"> + diff --git a/resin-doc/changes/changes.xtp b/resin-doc/changes/changes.xtp index ceb41b4f3..8604ffd95 100644 --- a/resin-doc/changes/changes.xtp +++ b/resin-doc/changes/changes.xtp @@ -12,6 +12,7 @@
    +
  • solaris: solaris needs #define _POSIX_PTHREAD_SEMANTICS for proper ctime_r behavior (#4791, rep by Alan Wright)
  • health: added health events and anomaly events (#4724)
  • db-pool: reduced default max-create-connections back to 5 (#4734)
  • db-pool: add min-idle-count to <database> configuration (#4664)