Resin Proxy Cache Support and REST support explained
From Resin 4.0 Wiki
Line 11: | Line 11: | ||
==JMX REST support== | ==JMX REST support== | ||
− | Resin has a nice API for managing Resin that is exposed via JMX. | + | Resin has a nice API for managing Resin that is exposed via JMX (Java Management Extensions). |
But in this day of interoperability, cloud, REST and DevOps, it is not good enough to have a JMX interface, one must have a server that can | But in this day of interoperability, cloud, REST and DevOps, it is not good enough to have a JMX interface, one must have a server that can | ||
be managed as a service via a REST interface. | be managed as a service via a REST interface. | ||
Line 19: | Line 19: | ||
==Let's create a simple page to demonstrate how the cache works== | ==Let's create a simple page to demonstrate how the cache works== | ||
Let's create a really simple JSP file so we can focus more on the REST, JMX, etc. setup. | Let's create a really simple JSP file so we can focus more on the REST, JMX, etc. setup. | ||
+ | Using a standard Resin 4.0.32 install, I place this file under '''/var/resin/webapps/ROOT/cache.jsp'''. | ||
+ | |||
+ | <pre> | ||
+ | <%@ page session="false" %> | ||
+ | <%! int counter; %> | ||
+ | <% | ||
+ | response.addHeader("Cache-Control", "max-age=150"); | ||
+ | %> | ||
+ | Count: <%= counter++ %> | ||
+ | </pre> | ||
+ | |||
+ | The above says cache the page for 150 seconds. If you enable Resin http proxy caching, Resin will cache this page for every client for 150 seconds. | ||
+ | Let's say, we changed the data that backs this page, and we want a faster update, how would we tell Resin to evict this page out of cache. | ||
+ | |||
+ | (Side note: Since Resin http proxy cache is built into Resin, it knows about every Java web resource and can cache them all without the need to use a cache providers special server side includes. Your cache includes can just be jsp:include and <%@include, etc.) | ||
+ | |||
+ | When you load this page, it gets put into the Resin cache. With just the above, it does not matter if the end users hits shift refresh from their browser or whatever, this page is going to stay in the cache. (There are ways to enable some cache validation checking to be triggered from client, but that is for another lesson as it involves more cache controls like ETag, etc.) | ||
+ | |||
+ | ==But first lets setup the http proxy cache== | ||
+ | |||
+ | |||
+ | To enable the cache, you just need to modify /etc/resin/resin.properties and make sure the following two properties get set: | ||
+ | |||
+ | <pre> | ||
+ | |||
+ | # Enable the proxy-cache - for caching static content in memory | ||
+ | proxy_cache_enable : true | ||
+ | |||
+ | # Sets the proxy cache memory size | ||
+ | proxy_cache_size : 256m cache | ||
+ | </pre> | ||
+ | |||
+ | Now the cache is setup, but like we mentioned earlier, there is no way to invalidate it. What if we know some process just happened and a certain page is invalid. What if you just did a data import or ran some batch job or received a new product xml file or whatever. Perhaps you have some backend script that stages this data, and now you need to kill this page out of the cache. Now in the Java world we have [http://wiki4.caucho.com/Resin_4_Application_Server_JMX_Tutorial JMX], and it is a nice way to interface with such things like caching, but what if these scripts were written in Ruby, Perl, Bash, or whatever. This is where the REST Admin service comes into play. | ||
+ | |||
+ | ==Turning on REST support== | ||
+ | To turn on the REST admin support, you guessed it, modify /etc/resin/resin.properties file as follows: | ||
+ | |||
+ | <pre> | ||
+ | # Enable Resin REST Admin | ||
+ | rest_admin_enable : true | ||
+ | |||
+ | # Require SSL for REST Admin | ||
+ | # rest_admin_ssl : true | ||
+ | </pre> | ||
+ | |||
+ | If you are not on an internal safe network, you should install open ssl support from the get go, and set '''rest_admin_ssl''' to true (it is commented out now). For now, I am going to assume that you are just messing around or you really trust your network's security and we will leave SSL support off. (We should come back and add it and change the examples accordingly.) | ||
+ | |||
+ | ==Setting up a user for the rest calls== | ||
+ | |||
+ | Run the generate-password command to create a new password. | ||
+ | |||
+ | <pre> | ||
+ | $ resinctl generate-password --user foobar --password foobar | ||
+ | </pre> | ||
+ | |||
+ | '''Output''' | ||
+ | <pre> | ||
+ | admin_user : foobar | ||
+ | admin_password : {SSHA}MW8h/2zwAk4Oqa3yfObDEKMcht3OKvil | ||
+ | </pre> | ||
+ | |||
+ | Modify /etc/resin/resin.properties and put those two entries in there. Now you have an admin user called foobar with the password foobar. | ||
+ | You can add more users just search /etc/resin/*.xml and see where $admin_user is getting used. | ||
+ | |||
+ | ==Invalidating the page from the command line with Curl== | ||
+ | |||
+ | If you don't see a Curl call to a REST example, then you should be suspicious. | ||
+ | Thus without further ado, let's show using Curl to invalidate our proxy cache via a REST call to the Resin REST admin. | ||
+ | |||
+ | The easier way to invalidate a page is to you use the curl command as follows: | ||
+ | |||
+ | <pre> | ||
+ | curl --user foobar:foobar | ||
+ | --data "values=.* .*cache.jsp" | ||
+ | "http://localhost:8080/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" | ||
+ | </pre> | ||
+ | |||
+ | (Newlines added for clarity). | ||
+ | This is the return, which is just JSON. | ||
+ | |||
+ | <pre> | ||
+ | {"bean":"resin:type=ProxyCache","operation":"clearCacheByPattern(java.lang.String, java.lang.String)","return-value":"null"} | ||
+ | </pre> | ||
+ | |||
+ | All Resin jmx beans are exposed. Thus all of Resin jmx support is open to REST calls. | ||
+ | |||
+ | The '''--data "values=*. *cache.jsp"''' passes two arguments to the jmx method '''ProxyCache::clearCacheByPattern'''. | ||
+ | The URL starts with the URI: '''/resin-rest/''' where you will find all of the Resin admin support. | ||
+ | The URI under this /resin-rest/'''jmx-call''' (jmx-call) means we want to invoke a JMX method. | ||
+ | The query param '''pattern=resin:type=ProxyCache''' means we want to call a method on the JMX bean '''resin:ProxyCache''' | ||
+ | The query param '''operation=clearCacheByPattern''' means we want to call the '''clearCacheByPattern''' method. | ||
+ | |||
+ | You can also use curl to submit SSL/TLS requests. After you run this from a bash script or Perl you can go the test cache.jsp and it will reload as expected. | ||
+ | |||
+ | (see http://javadoc4.caucho.com/com/caucho/management/server/ProxyCacheMXBean.html) | ||
+ | |||
+ | |||
+ | ==REST calls from other languages== | ||
+ | Here are some other example from other languages making REST calls. | ||
+ | |||
+ | |||
+ | ===Ruby Rest call to Resin JMX=== | ||
+ | <pre> | ||
+ | require "net/http" | ||
+ | require "uri" | ||
+ | |||
+ | $values = ".* .*cache.jsp" | ||
+ | $clearCacheByPatternURL = "http://localhost:8080/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" | ||
+ | $connected=false | ||
+ | |||
+ | def main | ||
+ | |||
+ | begin | ||
+ | response = Net::HTTP.get_response(URI.parse("http://localhost:8080/")) | ||
+ | puts response.body | ||
+ | connected=true | ||
+ | rescue Net::HTTPExceptions => ex | ||
+ | connected=false | ||
+ | puts ("The error was " + ex) | ||
+ | ensure | ||
+ | #conn.close() unless conn.nil? | ||
+ | end | ||
+ | |||
+ | if connected | ||
+ | invalidateCache() | ||
+ | else | ||
+ | puts ("Resin does not appear to be up") | ||
+ | end | ||
+ | end | ||
+ | |||
+ | |||
+ | def invalidateCache() | ||
+ | begin | ||
+ | uri = URI.parse($clearCacheByPatternURL) | ||
+ | http = Net::HTTP.new(uri.host, uri.port) | ||
+ | request = Net::HTTP::Post.new(uri.request_uri) | ||
+ | request.basic_auth("foobar", "foobar") | ||
+ | request.set_form_data({"values" => $values}) | ||
+ | response = http.request(request) | ||
+ | puts response.body | ||
+ | puts "No error " + response.code | ||
+ | rescue Net::HTTPExceptions => ex | ||
+ | connected=false | ||
+ | puts ("The error was " + ex) | ||
+ | ensure | ||
+ | #http.close() unless http.nil? | ||
+ | end | ||
+ | |||
+ | end | ||
+ | |||
+ | if __FILE__ == $0 | ||
+ | main | ||
+ | end | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===Python Rest call to Resin REST Admin=== | ||
+ | <pre> | ||
+ | import httplib | ||
+ | import base64 | ||
+ | |||
+ | requestBody = "values=.* .*cache.jsp" | ||
+ | host = "localhost" | ||
+ | clearCacheByPatternURI = "/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" | ||
+ | connected=False | ||
+ | |||
+ | def main(): | ||
+ | global connected | ||
+ | try: | ||
+ | conn = httplib.HTTPConnection(host, port=8080) | ||
+ | conn.request("GET", "/") | ||
+ | response = conn.getresponse() | ||
+ | data = response.read() | ||
+ | print (data) | ||
+ | connected=True | ||
+ | except httplib.HTTPException, ex: | ||
+ | connected=False | ||
+ | print ("The error was %s" % ex) | ||
+ | finally: | ||
+ | if conn: | ||
+ | conn.close() | ||
+ | |||
+ | if connected: | ||
+ | invalidateCache() | ||
+ | else: | ||
+ | print ("Resin does not appear to be up") | ||
+ | |||
+ | def invalidateCache(): | ||
+ | auth = base64.encodestring("foobar:foobar") | ||
+ | headers = {"Authorization" : "Basic %s" % auth, | ||
+ | "Content-Type": "application/x-www-form-urlencoded"} | ||
+ | |||
+ | try: | ||
+ | conn = httplib.HTTPConnection(host, port=8080) | ||
+ | conn.request("POST", clearCacheByPatternURI, requestBody, headers) | ||
+ | response = conn.getresponse() | ||
+ | data = response.read() | ||
+ | print (data) | ||
+ | print ("No error %s " % response.status) | ||
+ | except httplib.HTTPException, ex: | ||
+ | print ("err was %s" % ex) | ||
+ | finally: | ||
+ | if conn: | ||
+ | conn.close() | ||
+ | |||
+ | if __name__ =='__main__': | ||
+ | main() | ||
+ | |||
+ | |||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===PERL Rest call to Resin REST Admin=== | ||
+ | <pre> | ||
+ | coming soon | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===Java Rest call to Resin REST Admin=== | ||
+ | <pre> | ||
+ | coming soon | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | ===Go programming Rest call to Resin REST Admin=== | ||
+ | <pre> | ||
+ | package main | ||
+ | |||
+ | import ( | ||
+ | "fmt" | ||
+ | "net/http" | ||
+ | "io/ioutil" | ||
+ | "bytes" | ||
+ | ) | ||
+ | |||
+ | var ( | ||
+ | connected bool | ||
+ | client *http.Client = &http.Client{} | ||
+ | requestBody *bytes.Buffer = bytes.NewBufferString("values=.* .*cache.jsp") | ||
+ | clearCacheByPatternURL string = "http://localhost:8080/resin-rest/jmx-call" + | ||
+ | "?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" | ||
+ | ) | ||
+ | |||
+ | func main() { | ||
+ | |||
+ | if resp, err := http.Get("http://localhost:8080/"); err==nil { | ||
+ | |||
+ | if body, err := ioutil.ReadAll(resp.Body); err==nil { | ||
+ | fmt.Println(string(body)) | ||
+ | connected = true | ||
+ | } | ||
+ | |||
+ | } else { | ||
+ | fmt.Println("The error was", err) | ||
+ | } | ||
+ | |||
+ | if connected { | ||
+ | invalidateCache(); | ||
+ | } else { | ||
+ | fmt.Println("Resin does not appear to be up"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | func invalidateCache() { | ||
+ | if request, err := http.NewRequest("POST", clearCacheByPatternURL, requestBody); err==nil { | ||
+ | fmt.Println(request.Method) | ||
+ | request.SetBasicAuth("foobar", "foobar") | ||
+ | request.Header.Add("Content-Type", "application/x-www-form-urlencoded") | ||
+ | response, err := client.Do(request) | ||
+ | |||
+ | if body, err := ioutil.ReadAll(response.Body); err==nil { | ||
+ | fmt.Println(string(body)) | ||
+ | connected = true | ||
+ | } | ||
+ | if err==nil{ | ||
+ | fmt.Println("No error " + response.Status) | ||
+ | } else { | ||
+ | fmt.Println("err was", err) | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | ==Further reading== | ||
+ | ** [http://blog.caucho.com/2009/11/17/resin-rest-adminstration-interface/ Resin REST support] | ||
+ | ** [http://www.caucho.com/resin-4.0/admin/resin-admin-rest.xtp Resin REST Admin page] |
Latest revision as of 00:00, 12 January 2013
[edit] Resin provides real DevOps support
Resin is an end to end solution from load balancer, to http proxy cache, to cloud deployment. This is not to say that you could not use Resin with NginX or Varnish or XYZ, because you can. This is to say that Resin's built in support is typically the fastest most scalable, most supportable option. To demonstrate this, let's show how we can use Resin built-in proxy cache (similar to Varnish or Squid) with Resin's REST admin support.
This article will not only help you with using our Proxy Cache Support, but also with learning about our REST administrations, the envy of DevOps who have to support Java everywhere.
[edit] JMX REST support
Resin has a nice API for managing Resin that is exposed via JMX (Java Management Extensions). But in this day of interoperability, cloud, REST and DevOps, it is not good enough to have a JMX interface, one must have a server that can be managed as a service via a REST interface.
All JMX operations and quite a few other operations are available via our REST admin interface as well as JMX and CLI.
[edit] Let's create a simple page to demonstrate how the cache works
Let's create a really simple JSP file so we can focus more on the REST, JMX, etc. setup. Using a standard Resin 4.0.32 install, I place this file under /var/resin/webapps/ROOT/cache.jsp.
<%@ page session="false" %> <%! int counter; %> <% response.addHeader("Cache-Control", "max-age=150"); %> Count: <%= counter++ %>
The above says cache the page for 150 seconds. If you enable Resin http proxy caching, Resin will cache this page for every client for 150 seconds. Let's say, we changed the data that backs this page, and we want a faster update, how would we tell Resin to evict this page out of cache.
(Side note: Since Resin http proxy cache is built into Resin, it knows about every Java web resource and can cache them all without the need to use a cache providers special server side includes. Your cache includes can just be jsp:include and <%@include, etc.)
When you load this page, it gets put into the Resin cache. With just the above, it does not matter if the end users hits shift refresh from their browser or whatever, this page is going to stay in the cache. (There are ways to enable some cache validation checking to be triggered from client, but that is for another lesson as it involves more cache controls like ETag, etc.)
[edit] But first lets setup the http proxy cache
To enable the cache, you just need to modify /etc/resin/resin.properties and make sure the following two properties get set:
# Enable the proxy-cache - for caching static content in memory proxy_cache_enable : true # Sets the proxy cache memory size proxy_cache_size : 256m cache
Now the cache is setup, but like we mentioned earlier, there is no way to invalidate it. What if we know some process just happened and a certain page is invalid. What if you just did a data import or ran some batch job or received a new product xml file or whatever. Perhaps you have some backend script that stages this data, and now you need to kill this page out of the cache. Now in the Java world we have JMX, and it is a nice way to interface with such things like caching, but what if these scripts were written in Ruby, Perl, Bash, or whatever. This is where the REST Admin service comes into play.
[edit] Turning on REST support
To turn on the REST admin support, you guessed it, modify /etc/resin/resin.properties file as follows:
# Enable Resin REST Admin rest_admin_enable : true # Require SSL for REST Admin # rest_admin_ssl : true
If you are not on an internal safe network, you should install open ssl support from the get go, and set rest_admin_ssl to true (it is commented out now). For now, I am going to assume that you are just messing around or you really trust your network's security and we will leave SSL support off. (We should come back and add it and change the examples accordingly.)
[edit] Setting up a user for the rest calls
Run the generate-password command to create a new password.
$ resinctl generate-password --user foobar --password foobar
Output
admin_user : foobar admin_password : {SSHA}MW8h/2zwAk4Oqa3yfObDEKMcht3OKvil
Modify /etc/resin/resin.properties and put those two entries in there. Now you have an admin user called foobar with the password foobar. You can add more users just search /etc/resin/*.xml and see where $admin_user is getting used.
[edit] Invalidating the page from the command line with Curl
If you don't see a Curl call to a REST example, then you should be suspicious. Thus without further ado, let's show using Curl to invalidate our proxy cache via a REST call to the Resin REST admin.
The easier way to invalidate a page is to you use the curl command as follows:
curl --user foobar:foobar --data "values=.* .*cache.jsp" "http://localhost:8080/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern"
(Newlines added for clarity). This is the return, which is just JSON.
{"bean":"resin:type=ProxyCache","operation":"clearCacheByPattern(java.lang.String, java.lang.String)","return-value":"null"}
All Resin jmx beans are exposed. Thus all of Resin jmx support is open to REST calls.
The --data "values=*. *cache.jsp" passes two arguments to the jmx method ProxyCache::clearCacheByPattern. The URL starts with the URI: /resin-rest/ where you will find all of the Resin admin support. The URI under this /resin-rest/jmx-call (jmx-call) means we want to invoke a JMX method. The query param pattern=resin:type=ProxyCache means we want to call a method on the JMX bean resin:ProxyCache The query param operation=clearCacheByPattern means we want to call the clearCacheByPattern method.
You can also use curl to submit SSL/TLS requests. After you run this from a bash script or Perl you can go the test cache.jsp and it will reload as expected.
(see http://javadoc4.caucho.com/com/caucho/management/server/ProxyCacheMXBean.html)
[edit] REST calls from other languages
Here are some other example from other languages making REST calls.
[edit] Ruby Rest call to Resin JMX
require "net/http" require "uri" $values = ".* .*cache.jsp" $clearCacheByPatternURL = "http://localhost:8080/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" $connected=false def main begin response = Net::HTTP.get_response(URI.parse("http://localhost:8080/")) puts response.body connected=true rescue Net::HTTPExceptions => ex connected=false puts ("The error was " + ex) ensure #conn.close() unless conn.nil? end if connected invalidateCache() else puts ("Resin does not appear to be up") end end def invalidateCache() begin uri = URI.parse($clearCacheByPatternURL) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Post.new(uri.request_uri) request.basic_auth("foobar", "foobar") request.set_form_data({"values" => $values}) response = http.request(request) puts response.body puts "No error " + response.code rescue Net::HTTPExceptions => ex connected=false puts ("The error was " + ex) ensure #http.close() unless http.nil? end end if __FILE__ == $0 main end
[edit] Python Rest call to Resin REST Admin
import httplib import base64 requestBody = "values=.* .*cache.jsp" host = "localhost" clearCacheByPatternURI = "/resin-rest/jmx-call?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" connected=False def main(): global connected try: conn = httplib.HTTPConnection(host, port=8080) conn.request("GET", "/") response = conn.getresponse() data = response.read() print (data) connected=True except httplib.HTTPException, ex: connected=False print ("The error was %s" % ex) finally: if conn: conn.close() if connected: invalidateCache() else: print ("Resin does not appear to be up") def invalidateCache(): auth = base64.encodestring("foobar:foobar") headers = {"Authorization" : "Basic %s" % auth, "Content-Type": "application/x-www-form-urlencoded"} try: conn = httplib.HTTPConnection(host, port=8080) conn.request("POST", clearCacheByPatternURI, requestBody, headers) response = conn.getresponse() data = response.read() print (data) print ("No error %s " % response.status) except httplib.HTTPException, ex: print ("err was %s" % ex) finally: if conn: conn.close() if __name__ =='__main__': main()
[edit] PERL Rest call to Resin REST Admin
coming soon
[edit] Java Rest call to Resin REST Admin
coming soon
[edit] Go programming Rest call to Resin REST Admin
package main import ( "fmt" "net/http" "io/ioutil" "bytes" ) var ( connected bool client *http.Client = &http.Client{} requestBody *bytes.Buffer = bytes.NewBufferString("values=.* .*cache.jsp") clearCacheByPatternURL string = "http://localhost:8080/resin-rest/jmx-call" + "?pattern=resin:type=ProxyCache&operation=clearCacheByPattern" ) func main() { if resp, err := http.Get("http://localhost:8080/"); err==nil { if body, err := ioutil.ReadAll(resp.Body); err==nil { fmt.Println(string(body)) connected = true } } else { fmt.Println("The error was", err) } if connected { invalidateCache(); } else { fmt.Println("Resin does not appear to be up"); } } func invalidateCache() { if request, err := http.NewRequest("POST", clearCacheByPatternURL, requestBody); err==nil { fmt.Println(request.Method) request.SetBasicAuth("foobar", "foobar") request.Header.Add("Content-Type", "application/x-www-form-urlencoded") response, err := client.Do(request) if body, err := ioutil.ReadAll(response.Body); err==nil { fmt.Println(string(body)) connected = true } if err==nil{ fmt.Println("No error " + response.Status) } else { fmt.Println("err was", err) } } }