Caching of Seam Remoting Interface Components

View: New views
13 Messages — Rating Filter:   Alert me  

Caching of Seam Remoting Interface Components

by Ashish Tonse :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

All,

  I am currently working on implementing Seam on part of a relatively high traffic site (8k+ concurrent users) and optimizing our backend and frontend to handle that traffic. When focusing on front-end, I'm trying to make sure that the right HTTP caching headers are sent out -- but our Seam Remoting javascript files can't be browser-cached for a couple of reasons:

  /interface.js?componentName won't get cached due to it containing a query string and the way browsers handle those
  /remote.js always returns an HTTP 200 with the actual file contents, whereas this file would only change with new Seam releases (is that correct?)

  Also, neither of these JS files are combined, packed or minified.

  The Seam Remoting InterfaceGenerator does have an interface cache for each component, so at least that time is saved. But it still sends back unnecessary unchanged content to the browser. Since javascript downloads block all other downloads in the browser, this is especially noticeable with load times, making the page seem slower.

  A good potential solution (suggested by Dan Allen on twitter) to the first issue... use the ReWrite filter. My guess would be something like this:

  /interface/componentName.js --> interface.js?componentName

  And if we sent the right HTTP caching headers, that would fix that issue and browsers could cache it. But what about using multiple components? This is what I propose...

  /interface/componentName1,componentName2,componentName3.js -----> /interface.js?componentName1,componentName2,componentName3

  these still are valid and "RESTful" URLs... and can be cached.

  Is this something reasonable enough that I can take on as my first Seam contribution? (Seems small enough)... 

  Furthermore, in non-debug mode, Seam should send back minified Javascript (using the Java based YUI-compressor)

  Please let me know your thoughts before I get started,

Thanks,

Ashish Tonse
Kaizen Consulting

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Dan Allen (mojavelinux) :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Thu, Jun 25, 2009 at 3:42 PM, Ashish Tonse <ashish.tonse@...> wrote:

  I am currently working on implementing Seam on part of a relatively high traffic site (8k+ concurrent users) and optimizing our backend and frontend to handle that traffic. When focusing on front-end, I'm trying to make sure that the right HTTP caching headers are sent out -- but our Seam Remoting javascript files can't be browser-cached for a couple of reasons:

  /interface.js?componentName won't get cached due to it containing a query string and the way browsers handle those
  /remote.js always returns an HTTP 200 with the actual file contents, whereas this file would only change with new Seam releases (is that correct?)

  Also, neither of these JS files are combined, packed or minified.

Yep, you have identified one of the key optimizations that really needs to be addressed in Seam.
 

  The Seam Remoting InterfaceGenerator does have an interface cache for each component, so at least that time is saved. But it still sends back unnecessary unchanged content to the browser. Since javascript downloads block all other downloads in the browser, this is especially noticeable with load times, making the page seem slower.

  A good potential solution (suggested by Dan Allen on twitter) to the first issue... use the ReWrite filter. My guess would be something like this:

  /interface/componentName.js --> interface.js?componentName

  And if we sent the right HTTP caching headers, that would fix that issue and browsers could cache it. But what about using multiple components? This is what I propose...

  /interface/componentName1,componentName2,componentName3.js -----> /interface.js?componentName1,componentName2,componentName3

Why not go with something a little more natural? Such as:

/seam/resource/componentName1/componentName2/componentName3/interface.js

I wonder if the extension is even necessary since the servlet is being matched using the /seam/resource/* pattern.

Christian and Jozef, any thoughts on the URL design here?

 

  Is this something reasonable enough that I can take on as my first Seam contribution? (Seems small enough)... 

Sure thing. You have to start somewhere, right?


  Furthermore, in non-debug mode, Seam should send back minified Javascript (using the Java based YUI-compressor)

Absolutely.

-Dan

--
Dan Allen
Senior Software Engineer, Red Hat | Author of Seam in Action

http://mojavelinux.com
http://mojavelinux.com/seaminaction
http://in.relation.to/Bloggers/Dan

NOTE: While I make a strong effort to keep up with my email on a daily
basis, personal or other work matters can sometimes keep me away
from my email. If you contact me, but don't hear back for more than a week,
it is very likely that I am excessively backlogged or the message was
caught in the spam filters.  Please don't hesitate to resend a message if
you feel that it did not reach my attention.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Pete Muir-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On 25 Jun 2009, at 21:16, Dan Allen wrote:

> On Thu, Jun 25, 2009 at 3:42 PM, Ashish Tonse  
> <ashish.tonse@...> wrote:
>
>   I am currently working on implementing Seam on part of a  
> relatively high traffic site (8k+ concurrent users) and optimizing  
> our backend and frontend to handle that traffic. When focusing on  
> front-end, I'm trying to make sure that the right HTTP caching  
> headers are sent out -- but our Seam Remoting javascript files can't  
> be browser-cached for a couple of reasons:
>
>   /interface.js?componentName won't get cached due to it containing  
> a query string and the way browsers handle those
>   /remote.js always returns an HTTP 200 with the actual file  
> contents, whereas this file would only change with new Seam releases  
> (is that correct?)
>
>   Also, neither of these JS files are combined, packed or minified.
>
> Yep, you have identified one of the key optimizations that really  
> needs to be addressed in Seam.
>
>
>   The Seam Remoting InterfaceGenerator does have an interface cache  
> for each component, so at least that time is saved. But it still  
> sends back unnecessary unchanged content to the browser. Since  
> javascript downloads block all other downloads in the browser, this  
> is especially noticeable with load times, making the page seem slower.
>
>   A good potential solution (suggested by Dan Allen on twitter) to  
> the first issue... use the ReWrite filter. My guess would be  
> something like this:
>
>   /interface/componentName.js --> interface.js?componentName
>
>   And if we sent the right HTTP caching headers, that would fix that  
> issue and browsers could cache it. But what about using multiple  
> components? This is what I propose...
>
>   /interface/componentName1,componentName2,componentName3.js -----> /
> interface.js?componentName1,componentName2,componentName3
>
> Why not go with something a little more natural? Such as:
>
> /seam/resource/componentName1/componentName2/componentName3/
> interface.js

This isn't right, as it implies hierarchy where there isn't any  
(component 2 isn't a child of component1). You should use either a  
command or a semi-colon - the general recommendation is to use a comma  
if the order is important, semi-colon if it isn't (it isn't here).

I would suggest /interface.js/component1;component2 etc.

>
> I wonder if the extension is even necessary since the servlet is  
> being matched using the /seam/resource/* pattern.

Yes, keep it, as it adds information to the URL.

>
> Christian and Jozef, any thoughts on the URL design here?
>
>
>
>   Is this something reasonable enough that I can take on as my first  
> Seam contribution? (Seems small enough)...
>
> Sure thing. You have to start somewhere, right?
>
>
>   Furthermore, in non-debug mode, Seam should send back minified  
> Javascript (using the Java based YUI-compressor)
>
> Absolutely.

Make sure this is optional :-) Not everyone wants to be forced to add  
this dependency.

So, if YUI-compressor is available AND not in debug mode...

>
> -Dan
>
> --
> Dan Allen
> Senior Software Engineer, Red Hat | Author of Seam in Action
>
> http://mojavelinux.com
> http://mojavelinux.com/seaminaction
> http://in.relation.to/Bloggers/Dan
>
> NOTE: While I make a strong effort to keep up with my email on a daily
> basis, personal or other work matters can sometimes keep me away
> from my email. If you contact me, but don't hear back for more than  
> a week,
> it is very likely that I am excessively backlogged or the message was
> caught in the spam filters.  Please don't hesitate to resend a  
> message if
> you feel that it did not reach my attention.
> _______________________________________________
> seam-dev mailing list
> seam-dev@...
> https://lists.jboss.org/mailman/listinfo/seam-dev

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Christian Bauer-8 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Jun 25, 2009, at 22:35 , Pete Muir wrote:

> I would suggest /interface.js/component1;component2 etc.

I'd recommend /component1;component2/interface.js which leaves the URI  
design open for further sub-resources (of maybe different  
representation types) that describe component(s).

Reply to some of the other questions in this thread: I've implemented  
a ConditionalAbstractResource that we'll use in the future to send  
304's when a request comes in and the resource state hasn't been  
modified. I'm now adding a caching layer so we can also control _when_  
and _if_ a conditional request should be used. But I agree that it  
would be best to not use a query param for the interface.js resource  
as that would negate all of the conditional/cache efforts.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Pete Muir-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On 25 Jun 2009, at 22:29, Christian Bauer wrote:

>
> On Jun 25, 2009, at 22:35 , Pete Muir wrote:
>
>> I would suggest /interface.js/component1;component2 etc.
>
> I'd recommend /component1;component2/interface.js which leaves the  
> URI design open for further sub-resources (of maybe different  
> representation types) that describe component(s).

Much better :-)

> Reply to some of the other questions in this thread: I've  
> implemented a ConditionalAbstractResource that we'll use in the  
> future to send 304's when a request comes in and the resource state  
> hasn't been modified. I'm now adding a caching layer so we can also  
> control _when_ and _if_ a conditional request should be used. But I  
> agree that it would be best to not use a query param for the  
> interface.js resource as that would negate all of the conditional/
> cache efforts.
>
> _______________________________________________
> seam-dev mailing list
> seam-dev@...
> https://lists.jboss.org/mailman/listinfo/seam-dev

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Dan Allen (mojavelinux) :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Thu, Jun 25, 2009 at 6:58 PM, Pete Muir <pmuir@...> wrote:

On 25 Jun 2009, at 22:29, Christian Bauer wrote:


On Jun 25, 2009, at 22:35 , Pete Muir wrote:

I would suggest /interface.js/component1;component2 etc.

I'd recommend /component1;component2/interface.js which leaves the URI design open for further sub-resources (of maybe different representation types) that describe component(s).

Much better :-)

+1

--
Dan Allen
Senior Software Engineer, Red Hat | Author of Seam in Action

http://mojavelinux.com
http://mojavelinux.com/seaminaction
http://in.relation.to/Bloggers/Dan

NOTE: While I make a strong effort to keep up with my email on a daily
basis, personal or other work matters can sometimes keep me away
from my email. If you contact me, but don't hear back for more than a week,
it is very likely that I am excessively backlogged or the message was
caught in the spam filters.  Please don't hesitate to resend a message if
you feel that it did not reach my attention.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Ashish Tonse :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Lots more questions:

Christian: In addition to support for "304 Not Modified", are you planning to support http cache headers like either "Cache-Control" or "Expires"? (that way, the browser doesn't even perform a server roundtrip). And can I find a copy of your work so far in svn to serve as a guide?

Chris Wash on the list suggested looking at PackTag -- it looks to have a lot of logic for ETag support, already so might not need to rewrite some algorithms there.


Pete: How would I check if the jar exists?   try/catch Class.forName ?

Anyone:

  1) I started on this last night and the RequestHandlerFactory has a mapping for "/interface.js" to InterfaceGenerator -- How can I map a wildcard this way? I tried "*/interface.js" and a couple other types but they didn't work. Any tips to point me in the right direction for custom request handlers?

  2) Regarding OSS licensing... do we have to be wary of the licenses of dependencies (yui, packtag, etc) to make sure their licenses are compatible? Is there any JBoss contributor documentation on this?

Thanks,

Ashish

On Thu, Jun 25, 2009 at 7:09 PM, Dan Allen <dan.j.allen@...> wrote:
On Thu, Jun 25, 2009 at 6:58 PM, Pete Muir <pmuir@...> wrote:

On 25 Jun 2009, at 22:29, Christian Bauer wrote:


On Jun 25, 2009, at 22:35 , Pete Muir wrote:

I would suggest /interface.js/component1;component2 etc.

I'd recommend /component1;component2/interface.js which leaves the URI design open for further sub-resources (of maybe different representation types) that describe component(s).

Much better :-)

+1

--
Dan Allen
Senior Software Engineer, Red Hat | Author of Seam in Action

http://mojavelinux.com
http://mojavelinux.com/seaminaction
http://in.relation.to/Bloggers/Dan

NOTE: While I make a strong effort to keep up with my email on a daily
basis, personal or other work matters can sometimes keep me away
from my email. If you contact me, but don't hear back for more than a week,
it is very likely that I am excessively backlogged or the message was
caught in the spam filters.  Please don't hesitate to resend a message if
you feel that it did not reach my attention.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev



_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Pete Muir-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On 26 Jun 2009, at 19:31, Ashish Tonse wrote:

> Lots more questions:
>
> Christian: In addition to support for "304 Not Modified", are you  
> planning to support http cache headers like either "Cache-Control"  
> or "Expires"? (that way, the browser doesn't even perform a server  
> roundtrip). And can I find a copy of your work so far in svn to  
> serve as a guide?
>
> Chris Wash on the list suggested looking at PackTag -- it looks to  
> have a lot of logic for ETag support, already so might not need to  
> rewrite some algorithms there.
>
>
> Pete: How would I check if the jar exists?   try/catch Class.forName ?

Write it using @Install(classDependencies)

>
> Anyone:
>
>   1) I started on this last night and the RequestHandlerFactory has  
> a mapping for "/interface.js" to InterfaceGenerator -- How can I map  
> a wildcard this way? I tried "*/interface.js" and a couple other  
> types but they didn't work. Any tips to point me in the right  
> direction for custom request handlers?
>
>   2) Regarding OSS licensing... do we have to be wary of the  
> licenses of dependencies (yui, packtag, etc) to make sure their  
> licenses are compatible? Is there any JBoss contributor  
> documentation on this?
>

It's up to you (the contributor) to ensure whatever you use is  
compatible with the LGPL license.

Ask about specifics if you are not sure.

> Thanks,
>
> Ashish
>
> On Thu, Jun 25, 2009 at 7:09 PM, Dan Allen <dan.j.allen@...>  
> wrote:
> On Thu, Jun 25, 2009 at 6:58 PM, Pete Muir <pmuir@...> wrote:
>
> On 25 Jun 2009, at 22:29, Christian Bauer wrote:
>
>
> On Jun 25, 2009, at 22:35 , Pete Muir wrote:
>
> I would suggest /interface.js/component1;component2 etc.
>
> I'd recommend /component1;component2/interface.js which leaves the  
> URI design open for further sub-resources (of maybe different  
> representation types) that describe component(s).
>
> Much better :-)
>
> +1
>
> --
> Dan Allen
> Senior Software Engineer, Red Hat | Author of Seam in Action
>
> http://mojavelinux.com
> http://mojavelinux.com/seaminaction
> http://in.relation.to/Bloggers/Dan
>
> NOTE: While I make a strong effort to keep up with my email on a daily
> basis, personal or other work matters can sometimes keep me away
> from my email. If you contact me, but don't hear back for more than  
> a week,
> it is very likely that I am excessively backlogged or the message was
> caught in the spam filters.  Please don't hesitate to resend a  
> message if
> you feel that it did not reach my attention.
>
> _______________________________________________
> seam-dev mailing list
> seam-dev@...
> https://lists.jboss.org/mailman/listinfo/seam-dev
>
>

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Dan Allen (mojavelinux) :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message



 2) Regarding OSS licensing... do we have to be wary of the licenses of dependencies (yui, packtag, etc) to make sure their licenses are compatible? Is there any JBoss contributor documentation on this?


It's up to you (the contributor) to ensure whatever you use is compatible with the LGPL license.

YUI is BSD, so I believe that is compatible with LGPL. Not sure about packtag.

-Dan


--
Dan Allen
Senior Software Engineer, Red Hat | Author of Seam in Action

http://mojavelinux.com
http://mojavelinux.com/seaminaction
http://in.relation.to/Bloggers/Dan

NOTE: While I make a strong effort to keep up with my email on a daily
basis, personal or other work matters can sometimes keep me away
from my email. If you contact me, but don't hear back for more than a week,
it is very likely that I am excessively backlogged or the message was
caught in the spam filters.  Please don't hesitate to resend a message if
you feel that it did not reach my attention.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Christian Bauer-8 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

(I didn't get this e-mail, please reply to the list in future.)

>> Christian: In addition to support for "304 Not Modified", are you  
>> planning to support http cache headers like either "Cache-Control"  
>> or "Expires"? (that way, the browser doesn't even perform a server  
>> roundtrip). And can I find a copy of your work so far in svn to  
>> serve as a guide?

Conditional request handling without cache control is kind of useless,  
so the answer to the first question is Yes.

I'll commit something next week, this is the finished conditional  
request part:

package org.jboss.seam.web;

import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.util.Resources;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.net.URLConnection;
import java.net.URL;
import java.lang.management.ManagementFactory;

/**
  * Subclass this resource if you want to be able to send the right  
response automatically to
  * any conditional <tt>GET</tt> or <tt>HEAD</tt> request. The  
typically usecase is as follows:
  * <p/>
  * <pre>
  * public class MyResource extends ConditionalAbstractResource {
  *
  *     public void getResource(final HttpServletRequest request,  
final HttpServletResponse response) {
  *         String resourceVersion = ... // Calculate current state as  
string
  *         or
  *         byte[] resourceVersion = ... // Calculate current state as  
bytes
  *
  *         String resourcePath = ... // Get the relative (to servlet)  
path of the requested resource
  *
  *         if ( !sendConditional(request,
  *                              response,
  *                              createdEntityTag(resourceVersion,  
false),
  *                              
getLastModifiedTimestamp(resourcePath) ) {
  *
  *             // Send the regular resource representation with 200  
OK etc.
  *         }
  *     }
  * }
  * </pre>
  * <p/>
  * Note that the <tt>getLastModifiedTimestamp()</tt> method is only  
supplied for convenience; it may not
  * return what you expect as the "last modification timestamp" of the  
given resource. In many cases you'd
  * rather calculate that timestamp yourself.
  * <p/>
  */
public abstract class ConditionalAbstractResource extends  
AbstractResource
{

    public static final String HEADER_LAST_MODIFIED = "Last-Modified";
    public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-
Since";

    public static final String HEADER_ETAG = "ETag";
    public static final String HEADER_IF_NONE_MATCH = "If-None-Match";

    private static final LogProvider log =  
Logging.getLogProvider(ConditionalAbstractResource.class);

    /**
     * Validates the request headers <tt>If-Modified-Since</tt> and  
<tt>If-None-Match</tt> to determine
     * if a <tt>304 NOT MODIFIED</tt> response can be send. If that is  
the case, this method will automatically
     * send the response and return <tt>true</tt>. If condition  
validation fails, it will not change the
     * response and return <tt>false</tt>.
     * <p/>
     * Note that both <tt>entityTag</tt> and <tt>lastModified</tt>  
arguments can be <tt>null</tt>. The validation
     * procedure and the outcome depends on what the client requested.  
If the client requires that both entity tags and
     * modification timestamps be validated, both arguments must be  
supplied to the method and they must match, for
     * a 304 response to be send.
     * <p/>
     * In addition to responding with <tt>304 NOT MODIFIED</tt> when  
conditions match, this method will also, if
     * arguments are not <tt>null</tt>, send the right entity tag and  
last modification timestamps with the response,
     * so that future requests from the client can be made conditional.
     * <p/>
     *
     * @param request         The usual HttpServletRequest for header  
retrieval.
     * @param response        The usual HttpServletResponse for header  
manipulation.
     * @param entityTag       An entity tag (weak or strong, in  
doublequotes), typically produced by hashing the content
     *                        of the resource representation. If  
<tt>null</tt>, no entity tag will be send and if
     *                        validation is requested by the client,  
no match for a NOT MODIFIED response will be possible.
     * @param lastModified    The timestamp in number of milliseconds  
since unix epoch when the resource was
     *                        last modified. If <tt>null</tt>, no last  
modification timestamp will be send  and if
     *                        validation is requested by the client,  
no match for a NOT MODIFIED response will be possible.
     * @return <tt>true</tt> if a <tt>304 NOT MODIFIED</tt> response  
status has been set, <tt>false</tt> if requested
     *         conditions were invalid given the current state of the  
resource.
     * @throws IOException If setting the response status failed.
     */
    public boolean sendConditional(HttpServletRequest request,
                                   HttpServletResponse response,
                                   String entityTag, Long  
lastModified) throws IOException
    {

       String noneMatchHeader = request.getHeader(HEADER_IF_NONE_MATCH);
       Long modifiedSinceHeader =  
request.getDateHeader(HEADER_IF_MODIFIED_SINCE); // Careful, returns  
-1 instead of null!

       boolean noneMatchValid = false;
       if (entityTag != null)
       {

          if (! (entityTag.startsWith("\"") || entityTag.startsWith("W/
\"")) && !entityTag.endsWith("\""))
          {
             throw new IllegalArgumentException("Entity tag is not  
properly formatted (or quoted): " + entityTag);
          }

          // Always send an entity tag with the response
          response.setHeader(HEADER_ETAG, entityTag);

          if (noneMatchHeader != null)
          {
             noneMatchValid =  
isNoneMatchConditionValid(noneMatchHeader, entityTag);
          }
       }

       boolean modifiedSinceValid = false;
       if (lastModified != null)
       {

          // Always send the last modified timestamp with the response
          response.setDateHeader(HEADER_LAST_MODIFIED, lastModified);

          if (modifiedSinceHeader != -1)
          {
             modifiedSinceValid =  
isModifiedSinceConditionValid(modifiedSinceHeader, lastModified);
          }

       }

       if (noneMatchHeader != null && modifiedSinceHeader != -1)
       {
          log.debug(HEADER_IF_NONE_MATCH + " and " +  
HEADER_IF_MODIFIED_SINCE + " must match");

          // If both are received, we must not return 304 unless doing  
so is consistent with both header fields in the request!
          if (noneMatchValid && modifiedSinceValid)
          {
             log.debug(HEADER_IF_NONE_MATCH + " and " +  
HEADER_IF_MODIFIED_SINCE + " conditions match, sending 304");
             response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
             return true;
          }
          else
          {
             log.debug(HEADER_IF_NONE_MATCH + " and " +  
HEADER_IF_MODIFIED_SINCE + " conditions do not match, not sending 304");
             return false;
          }
       }

       if (noneMatchHeader != null && noneMatchValid)
       {
          log.debug(HEADER_IF_NONE_MATCH + " condition matches,  
sending 304");
          response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          return true;
       }

       if (modifiedSinceHeader != -1 && modifiedSinceValid)
       {
          log.debug(HEADER_IF_MODIFIED_SINCE + " condition matches,  
sending 304");
          response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
          return true;
       }

       log.debug("None of the cache conditions match, not sending 304");
       return false;
    }

    protected boolean isNoneMatchConditionValid(String  
noneMatchHeader, String entityTag)
    {
       if (noneMatchHeader.trim().equals("*"))
       {
          log.debug("Found * conditional request, hence current entity  
tag matches");
          return true;
       }
       String[] entityTagsArray = noneMatchHeader.trim().split(",");
       for (String requestTag : entityTagsArray)
       {
          if (requestTag.trim().equals(entityTag))
          {
             log.debug("Found matching entity tag in request");
             return true;
          }
       }
       log.debug("Resource has different entity tag than requested");
       return false;
    }

    protected boolean isModifiedSinceConditionValid(Long  
modifiedSinceHeader, Long lastModified)
    {
       if (lastModified <= modifiedSinceHeader)
       {
          log.debug("Resource has not been modified since requested  
timestamp");
          return true;
       }
       log.debug("Resource has been modified since requested  
timestamp");
       return false;
    }

    /**
     * Tries to get last modification timestamp of the resource by  
obtaining
     * a <tt>URLConnection</tt> to the file in the filesystem or JAR.
     *
     * @param resourcePath The relative (to the servlet) resource path.
     * @return Either the last modified filestamp or if an error  
occurs, the JVM system startup timestamp.
     */
    protected Long getLastModifiedTimestamp(String resourcePath)
    {
       try
       {
          // Try to load it from filesystem or JAR through URLConnection
          URL resourceURL = Resources.getResource(resourcePath,  
getServletContext());
          if (resourceURL == null)
          {
             // Fall back to startup time of the JVM
             return ManagementFactory.getRuntimeMXBean().getStartTime();
          }
          URLConnection resourceConn = resourceURL.openConnection();
          return resourceConn.getLastModified();
       }
       catch (Exception ex)
       {
          // Fall back to startup time of the JVM
          return ManagementFactory.getRuntimeMXBean().getStartTime();
       }
    }

    /**
     * Generates a (globally) unique identifier of the current state  
of the resource. The string will be
     * hashed with MD5 and the hash result is then formatted before it  
is returned. If <tt>null</tt>,
     * a <tt>null</tt> will be returned.
     *
     * @param hashSource The string source for hashing or the already  
hashed (strong or weak) entity tag.
     * @param weak       Set to <tt>true</tt> if you want a weak  
entity tag.
     * @return The hashed and formatted entity tag result.
     */
    protected String createEntityTag(String hashSource, boolean weak)
    {
       if (hashSource == null) return null;
       return (weak ? "W/\"" : "\"") + hash(hashSource, "UTF-8",  
"MD5") + "\"";
    }

    /**
     * Generates a (globally) unique identifier of the current state  
of the resource. The bytes will be
     * hashed with MD5 and the hash result is then formatted before it  
is returned. If <tt>null</tt>,
     * a <tt>null</tt> will be returned.
     *
     * @param hashSource The string source for hashing.
     * @param weak       Set to <tt>true</tt> if you want a weak  
entity tag.
     * @return The hashed and formatted entity tag result.
     */
    protected String createEntityTag(byte[] hashSource, boolean weak)
    {
       if (hashSource == null) return null;
       return (weak ? "W/\"" : "\"") + hash(hashSource, "MD5") + "\"";
    }

    protected String hash(String text, String charset, String algorithm)
    {
       try
       {
          return hash(text.getBytes(charset), algorithm);
       }
       catch (Exception e)
       {
          throw new RuntimeException(e);
       }
    }

    protected String hash(byte[] bytes, String algorithm)
    {
       try
       {
          MessageDigest md = MessageDigest.getInstance(algorithm);
          md.update(bytes);
          BigInteger number = new BigInteger(1, md.digest());
          StringBuffer sb = new StringBuffer("0");
          sb.append(number.toString(16));
          return sb.toString();
       }
       catch (Exception e)
       {
          throw new RuntimeException(e);
       }
    }

}

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Ashish Tonse :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

(My email has to be approved by a moderator, so if only Christian gets them, it's because the email hasn't gotten approved yet)

All, 
  Here's my first stab at Seam remoting interface.js caching (patch attached)... Should I be attaching this to a thread in a JIRA somewhere?

  I modified RequestHandlerFactory so it only checks for the last element of the path (after the last occurrence of '/')

  This allows for /remoting/arbitrary-string/interface.js to be handled by the handler for /interface.js and shouldn't break the handlers for /subscription and /poll 

  InterfaceGenerator checks to see that the path is valid, and extracts component names from the path. I haven't done any HTTP caching stuff yet. 

  Also, in the s:remote renderer, I am rendering %3B instead of ";" because tomcat requires the semicolons to be urlencoded. (Ref: https://issues.apache.org/bugzilla/show_bug.cgi?id=30535#c3)

  Regarding error messages, I think it would be more appropriate to send either a 404 (component not found) or 401 (Bad Request) or plain 500 status code when an invalid component or path is specified. Right now, I'm getting back a 200 OK, which is misleading. Thoughts?

  Please let me know if I'm on the right track re: code.

Thanks,

Ashish

On Fri, Jun 26, 2009 at 2:59 PM, Christian Bauer <christian.bauer@...> wrote:
(I didn't get this e-mail, please reply to the list in future.)


Christian: In addition to support for "304 Not Modified", are you planning to support http cache headers like either "Cache-Control" or "Expires"? (that way, the browser doesn't even perform a server roundtrip). And can I find a copy of your work so far in svn to serve as a guide?

Conditional request handling without cache control is kind of useless, so the answer to the first question is Yes.

I'll commit something next week, this is the finished conditional request part:

package org.jboss.seam.web;

import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.util.Resources;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.net.URLConnection;
import java.net.URL;
import java.lang.management.ManagementFactory;

/**
 * Subclass this resource if you want to be able to send the right response automatically to
 * any conditional <tt>GET</tt> or <tt>HEAD</tt> request. The typically usecase is as follows:
 * <p/>
 * <pre>
 * public class MyResource extends ConditionalAbstractResource {
 *
 *     public void getResource(final HttpServletRequest request, final HttpServletResponse response) {
 *         String resourceVersion = ... // Calculate current state as string
 *         or
 *         byte[] resourceVersion = ... // Calculate current state as bytes
 *
 *         String resourcePath = ... // Get the relative (to servlet) path of the requested resource
 *
 *         if ( !sendConditional(request,
 *                              response,
 *                              createdEntityTag(resourceVersion, false),
 *                              getLastModifiedTimestamp(resourcePath) ) {
 *
 *             // Send the regular resource representation with 200 OK etc.
 *         }
 *     }
 * }
 * </pre>
 * <p/>
 * Note that the <tt>getLastModifiedTimestamp()</tt> method is only supplied for convenience; it may not
 * return what you expect as the "last modification timestamp" of the given resource. In many cases you'd
 * rather calculate that timestamp yourself.
 * <p/>
 */
public abstract class ConditionalAbstractResource extends AbstractResource
{

  public static final String HEADER_LAST_MODIFIED = "Last-Modified";
  public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";

  public static final String HEADER_ETAG = "ETag";
  public static final String HEADER_IF_NONE_MATCH = "If-None-Match";

  private static final LogProvider log = Logging.getLogProvider(ConditionalAbstractResource.class);

  /**
   * Validates the request headers <tt>If-Modified-Since</tt> and <tt>If-None-Match</tt> to determine
   * if a <tt>304 NOT MODIFIED</tt> response can be send. If that is the case, this method will automatically
   * send the response and return <tt>true</tt>. If condition validation fails, it will not change the
   * response and return <tt>false</tt>.
   * <p/>
   * Note that both <tt>entityTag</tt> and <tt>lastModified</tt> arguments can be <tt>null</tt>. The validation
   * procedure and the outcome depends on what the client requested. If the client requires that both entity tags and
   * modification timestamps be validated, both arguments must be supplied to the method and they must match, for
   * a 304 response to be send.
   * <p/>
   * In addition to responding with <tt>304 NOT MODIFIED</tt> when conditions match, this method will also, if
   * arguments are not <tt>null</tt>, send the right entity tag and last modification timestamps with the response,
   * so that future requests from the client can be made conditional.
   * <p/>
   *
   * @param request         The usual HttpServletRequest for header retrieval.
   * @param response        The usual HttpServletResponse for header manipulation.
   * @param entityTag       An entity tag (weak or strong, in doublequotes), typically produced by hashing the content
   *                        of the resource representation. If <tt>null</tt>, no entity tag will be send and if
   *                        validation is requested by the client, no match for a NOT MODIFIED response will be possible.
   * @param lastModified    The timestamp in number of milliseconds since unix epoch when the resource was
   *                        last modified. If <tt>null</tt>, no last modification timestamp will be send  and if
   *                        validation is requested by the client, no match for a NOT MODIFIED response will be possible.
   * @return <tt>true</tt> if a <tt>304 NOT MODIFIED</tt> response status has been set, <tt>false</tt> if requested
   *         conditions were invalid given the current state of the resource.
   * @throws IOException If setting the response status failed.
   */
  public boolean sendConditional(HttpServletRequest request,
                                 HttpServletResponse response,
                                 String entityTag, Long lastModified) throws IOException
  {

     String noneMatchHeader = request.getHeader(HEADER_IF_NONE_MATCH);
     Long modifiedSinceHeader = request.getDateHeader(HEADER_IF_MODIFIED_SINCE); // Careful, returns -1 instead of null!

     boolean noneMatchValid = false;
     if (entityTag != null)
     {

        if (! (entityTag.startsWith("\"") || entityTag.startsWith("W/\"")) && !entityTag.endsWith("\""))
        {
           throw new IllegalArgumentException("Entity tag is not properly formatted (or quoted): " + entityTag);
        }

        // Always send an entity tag with the response
        response.setHeader(HEADER_ETAG, entityTag);

        if (noneMatchHeader != null)
        {
           noneMatchValid = isNoneMatchConditionValid(noneMatchHeader, entityTag);
        }
     }

     boolean modifiedSinceValid = false;
     if (lastModified != null)
     {

        // Always send the last modified timestamp with the response
        response.setDateHeader(HEADER_LAST_MODIFIED, lastModified);

        if (modifiedSinceHeader != -1)
        {
           modifiedSinceValid = isModifiedSinceConditionValid(modifiedSinceHeader, lastModified);
        }

     }

     if (noneMatchHeader != null && modifiedSinceHeader != -1)
     {
        log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_SINCE + " must match");

        // If both are received, we must not return 304 unless doing so is consistent with both header fields in the request!
        if (noneMatchValid && modifiedSinceValid)
        {
           log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_SINCE + " conditions match, sending 304");
           response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
           return true;
        }
        else
        {
           log.debug(HEADER_IF_NONE_MATCH + " and " + HEADER_IF_MODIFIED_SINCE + " conditions do not match, not sending 304");
           return false;
        }
     }

     if (noneMatchHeader != null && noneMatchValid)
     {
        log.debug(HEADER_IF_NONE_MATCH + " condition matches, sending 304");
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        return true;
     }

     if (modifiedSinceHeader != -1 && modifiedSinceValid)
     {
        log.debug(HEADER_IF_MODIFIED_SINCE + " condition matches, sending 304");
        response.sendError(HttpServletResponse.SC_NOT_MODIFIED);
        return true;
     }

     log.debug("None of the cache conditions match, not sending 304");
     return false;
  }

  protected boolean isNoneMatchConditionValid(String noneMatchHeader, String entityTag)
  {
     if (noneMatchHeader.trim().equals("*"))
     {
        log.debug("Found * conditional request, hence current entity tag matches");
        return true;
     }
     String[] entityTagsArray = noneMatchHeader.trim().split(",");
     for (String requestTag : entityTagsArray)
     {
        if (requestTag.trim().equals(entityTag))
        {
           log.debug("Found matching entity tag in request");
           return true;
        }
     }
     log.debug("Resource has different entity tag than requested");
     return false;
  }

  protected boolean isModifiedSinceConditionValid(Long modifiedSinceHeader, Long lastModified)
  {
     if (lastModified <= modifiedSinceHeader)
     {
        log.debug("Resource has not been modified since requested timestamp");
        return true;
     }
     log.debug("Resource has been modified since requested timestamp");
     return false;
  }

  /**
   * Tries to get last modification timestamp of the resource by obtaining
   * a <tt>URLConnection</tt> to the file in the filesystem or JAR.
   *
   * @param resourcePath The relative (to the servlet) resource path.
   * @return Either the last modified filestamp or if an error occurs, the JVM system startup timestamp.
   */
  protected Long getLastModifiedTimestamp(String resourcePath)
  {
     try
     {
        // Try to load it from filesystem or JAR through URLConnection
        URL resourceURL = Resources.getResource(resourcePath, getServletContext());
        if (resourceURL == null)
        {
           // Fall back to startup time of the JVM
           return ManagementFactory.getRuntimeMXBean().getStartTime();
        }
        URLConnection resourceConn = resourceURL.openConnection();
        return resourceConn.getLastModified();
     }
     catch (Exception ex)
     {
        // Fall back to startup time of the JVM
        return ManagementFactory.getRuntimeMXBean().getStartTime();
     }
  }

  /**
   * Generates a (globally) unique identifier of the current state of the resource. The string will be
   * hashed with MD5 and the hash result is then formatted before it is returned. If <tt>null</tt>,
   * a <tt>null</tt> will be returned.
   *
   * @param hashSource The string source for hashing or the already hashed (strong or weak) entity tag.
   * @param weak       Set to <tt>true</tt> if you want a weak entity tag.
   * @return The hashed and formatted entity tag result.
   */
  protected String createEntityTag(String hashSource, boolean weak)
  {
     if (hashSource == null) return null;
     return (weak ? "W/\"" : "\"") + hash(hashSource, "UTF-8", "MD5") + "\"";
  }

  /**
   * Generates a (globally) unique identifier of the current state of the resource. The bytes will be
   * hashed with MD5 and the hash result is then formatted before it is returned. If <tt>null</tt>,
   * a <tt>null</tt> will be returned.
   *
   * @param hashSource The string source for hashing.
   * @param weak       Set to <tt>true</tt> if you want a weak entity tag.
   * @return The hashed and formatted entity tag result.
   */
  protected String createEntityTag(byte[] hashSource, boolean weak)
  {
     if (hashSource == null) return null;
     return (weak ? "W/\"" : "\"") + hash(hashSource, "MD5") + "\"";
  }

  protected String hash(String text, String charset, String algorithm)
  {
     try
     {
        return hash(text.getBytes(charset), algorithm);
     }
     catch (Exception e)
     {
        throw new RuntimeException(e);
     }
  }

  protected String hash(byte[] bytes, String algorithm)
  {
     try
     {
        MessageDigest md = MessageDigest.getInstance(algorithm);
        md.update(bytes);
        BigInteger number = new BigInteger(1, md.digest());
        StringBuffer sb = new StringBuffer("0");
        sb.append(number.toString(16));
        return sb.toString();
     }
     catch (Exception e)
     {
        throw new RuntimeException(e);

     }
  }

}

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev



_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

restful-remoting.diff (4K) Download Attachment

Re: Caching of Seam Remoting Interface Components

by Christian Bauer-8 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


On Jul 01, 2009, at 06:32 , Ashish Tonse wrote:

>   Here's my first stab at Seam remoting interface.js caching (patch  
> attached)... Should I be attaching this to a thread in a JIRA  
> somewhere?

Yes, please attach to https://jira.jboss.org/jira/browse/JBSEAM-4186

>  Regarding error messages, I think it would be more appropriate to  
> send either a 404 (component not found) or 401 (Bad Request) or  
> plain 500 status code when an invalid component or path is  
> specified. Right now, I'm getting back a 200 OK, which is  
> misleading. Thoughts?

It should be 404 although I'm surprised that it isn't already.
_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev

Re: Caching of Seam Remoting Interface Components

by Ashish Tonse :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

ignore

On Wed, Jul 1, 2009 at 1:59 AM, Christian Bauer <christian.bauer@...> wrote:

On Jul 01, 2009, at 06:32 , Ashish Tonse wrote:

 Here's my first stab at Seam remoting interface.js caching (patch attached)... Should I be attaching this to a thread in a JIRA somewhere?

Yes, please attach to https://jira.jboss.org/jira/browse/JBSEAM-4186


 Regarding error messages, I think it would be more appropriate to send either a 404 (component not found) or 401 (Bad Request) or plain 500 status code when an invalid component or path is specified. Right now, I'm getting back a 200 OK, which is misleading. Thoughts?

It should be 404 although I'm surprised that it isn't already.

_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev


_______________________________________________
seam-dev mailing list
seam-dev@...
https://lists.jboss.org/mailman/listinfo/seam-dev