View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.renderkit.html.util;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.myfaces.shared_tomahawk.config.MyfacesConfig;
24  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
25  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlRendererUtils;
26  import org.apache.myfaces.shared_tomahawk.renderkit.html.HtmlResponseWriterImpl;
27  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
28  
29  import javax.faces.FacesException;
30  import javax.faces.context.FacesContext;
31  import javax.faces.context.ResponseWriter;
32  import javax.servlet.ServletContext;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  import java.io.IOException;
36  import java.io.UnsupportedEncodingException;
37  import java.net.URLEncoder;
38  import java.util.Map;
39  
40  /**
41   * @since 1.1.7
42   * @author Martin Marinschek
43   */
44  public class NonBufferingAddResource implements AddResource {
45  
46      protected static final String PATH_SEPARATOR = "/";
47      protected String _contextPath;
48  
49      private static final String RESOURCES_CACHE_KEY = AddResource.class.getName() + ".CACHE_KEY";
50  
51      private String resourceVirtualPath;
52  
53      protected Log log = LogFactory.getLog(NonBufferingAddResource.class);
54  
55      /**
56       * the context path for the web-app.<br />
57       * You can set the context path only once, every subsequent set will throw an SecurityException
58       */
59      public void setContextPath(String contextPath)
60      {
61          if (_contextPath != null)
62          {
63              throw new SecurityException("context path already set");
64          }
65  
66          _contextPath = contextPath;
67      }
68  
69      // Methods to add resources
70  
71      /**
72       * Insert a [script src="url"] entry at the current location in the response.
73       * The resource is expected to be in the classpath, at the same location as the
74       * specified component + "/resource".
75       * <p/>
76       * Example: when customComponent is class example.Widget, and
77       * resourceName is script.js, the resource will be retrieved from
78       * "example/Widget/resource/script.js" in the classpath.
79       */
80      public void addJavaScriptHere(FacesContext context, Class myfacesCustomComponent,
81                                    String resourceName) throws IOException
82      {
83          addJavaScriptHere(context, new MyFacesResourceHandler(myfacesCustomComponent, resourceName));
84      }
85  
86      /**
87       * Insert a [script src="url"] entry at the current location in the response.
88       *
89       * @param uri is the location of the desired resource, relative to the base
90       *            directory of the webapp (ie its contextPath).
91       */
92      public void addJavaScriptHere(FacesContext context, String uri) throws IOException
93      {
94          writeJavaScriptReference(context, getResourceUri(context, uri), true, false);
95      }
96  
97      protected static void writeJavaScriptReference(FacesContext context, String resourceUri, boolean encoding, boolean defer) throws IOException{
98          ResponseWriter writer = context.getResponseWriter();
99  
100         String src = null;
101         if(encoding) {
102             src=context.getExternalContext().encodeResourceURL(resourceUri);
103         }
104         else {
105             src = resourceUri;
106         }
107 
108         writeJavaScriptReference(defer, writer, src);
109     }
110 
111     protected static void writeJavaScriptReference(HttpServletResponse response, ResponseWriter writer, String resourceUri, boolean encoding, boolean defer) throws IOException {
112         String src = null;
113         if(encoding) {
114             src=response.encodeURL(resourceUri);
115         }
116         else {
117             src = resourceUri;
118         }
119 
120         writeJavaScriptReference(defer, writer, src);
121     }
122 
123     private static void writeJavaScriptReference(boolean defer, ResponseWriter writer, String src) throws IOException {
124         writer.startElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM, null);
125         writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
126 
127         if(defer) {
128             writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_ELEM_DEFER_ATTR, "true", null);
129         }
130         writer.writeURIAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SRC_ATTR, src, null);
131         writer.endElement(HTML.SCRIPT_ELEM);
132     }
133 
134     protected static void writeStyleReference(FacesContext context, String resourceUri) throws IOException {
135         ResponseWriter writer = context.getResponseWriter();
136         writer.startElement(HTML.LINK_ELEM, null);
137         writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.REL_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.STYLESHEET_VALUE, null);
138         writer.writeAttribute(HTML.HREF_ATTR, context.getExternalContext().encodeResourceURL(resourceUri), null);
139         writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
140         writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM);
141     }
142 
143     protected static void writeStyleReference(HttpServletResponse response, ResponseWriter writer,  String resourceUri) throws IOException {
144         writer.startElement(HTML.LINK_ELEM, null);
145         writer.writeAttribute(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.REL_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.STYLESHEET_VALUE, null);
146         writer.writeAttribute(HTML.HREF_ATTR, response.encodeURL(resourceUri), null);
147         writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
148         writer.endElement(org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.LINK_ELEM);
149     }
150 
151     protected static void writeInlineScript(ResponseWriter writer, String inlineScript) throws IOException {
152             writer.startElement(HTML.SCRIPT_ELEM, null);
153             writer.writeAttribute(HTML.SCRIPT_TYPE_ATTR, org.apache.myfaces.shared_tomahawk.renderkit.html.HTML.SCRIPT_TYPE_TEXT_JAVASCRIPT, null);
154             writer.writeText(inlineScript, null);
155             writer.endElement(HTML.SCRIPT_ELEM);
156     }
157 
158     protected static  void writeInlineStylesheet(ResponseWriter writer, String inlineStyle) throws IOException {
159         writer.startElement(HTML.STYLE_ELEM, null);
160         writer.writeAttribute(HTML.REL_ATTR, HTML.STYLESHEET_VALUE, null);
161         writer.writeAttribute(HTML.TYPE_ATTR, HTML.STYLE_TYPE_TEXT_CSS, null);
162         writer.writeText(inlineStyle, null);
163         writer.endElement(HTML.STYLE_ELEM);
164     }
165 
166     public void addJavaScriptHerePlain(FacesContext context, String uri) throws IOException
167     {
168         writeJavaScriptReference(context, getResourceUri(context, uri), false, false);
169     }
170 
171     /**
172      * Insert a [script src="url"] entry at the current location in the response.
173      *
174      * @param context The current faces-context
175      * @param resourceHandler is an object which specifies exactly how to build the url
176      *                        that is emitted into the script tag. Code which needs to generate URLs in ways
177      *                        that this class does not support by default can implement a custom ResourceHandler.
178      * @throws IOException
179      */
180     public void addJavaScriptHere(FacesContext context, ResourceHandler resourceHandler)
181             throws IOException
182     {
183         validateResourceHandler(resourceHandler);
184         writeJavaScriptReference(context, getResourceUri(context, resourceHandler), true, false);
185     }
186 
187     public void addResourceHere(FacesContext context, ResourceHandler resourceHandler)
188             throws IOException
189     {
190         validateResourceHandler(resourceHandler);
191 
192         String path = getResourceUri(context, resourceHandler);
193         ResponseWriter writer = context.getResponseWriter();
194         writer.write(context.getExternalContext().encodeResourceURL(path));
195     }
196 
197     /**
198      * Verify that the resource handler is acceptable. Null is not
199      * valid, and the getResourceLoaderClass method must return a
200      * Class object whose instances implements the ResourceLoader
201      * interface.
202      *
203      * @param resourceHandler handler to check
204      */
205     protected void validateResourceHandler(ResourceHandler resourceHandler)
206     {
207         if (resourceHandler == null)
208         {
209             throw new IllegalArgumentException("ResourceHandler is null");
210         }
211         validateResourceLoader(resourceHandler.getResourceLoaderClass());
212     }
213 
214     /**
215      * Given a Class object, verify that the instances of that class
216      * implement the ResourceLoader interface.
217      *
218      * @param resourceloader loader to check
219      */
220     protected void validateResourceLoader(Class resourceloader)
221     {
222         if (!ResourceLoader.class.isAssignableFrom(resourceloader))
223         {
224             throw new FacesException("Class " + resourceloader.getName() + " must implement "
225                     + ResourceLoader.class.getName());
226         }
227     }
228 
229     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) {
230         try {
231             writeJavaScriptReference(context,getResourceUri(context,resourceHandler),true,false);
232         } catch (IOException e) {
233             throw new IllegalStateException(e.getMessage());
234         }
235     }
236 
237     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
238         try {
239             writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(
240                     myfacesCustomComponent, resourceName)),true,false);
241         } catch (IOException e) {
242             throw new IllegalStateException(e.getMessage());
243         }
244     }
245 
246     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName, boolean defer) {
247         try {
248             writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(
249                     myfacesCustomComponent, resourceName)),true,true);
250         } catch (IOException e) {
251             throw new IllegalStateException(e.getMessage());
252         }
253     }
254 
255     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri) {
256         try {
257             writeJavaScriptReference(context,getResourceUri(context,uri),true,false);
258         } catch (IOException e) {
259             throw new IllegalStateException(e.getMessage());
260         }
261     }
262 
263     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, String uri, boolean defer) {
264         try {
265             writeJavaScriptReference(context,getResourceUri(context,uri),true,true);
266         } catch (IOException e) {
267             throw new IllegalStateException(e.getMessage());
268         }
269     }
270 
271     /**
272      *
273      * @param context
274      * @param javascriptEventName
275      * @param addedJavaScript
276      *
277      * @deprecated
278      */
279     public void addJavaScriptToBodyTag(FacesContext context, String javascriptEventName, String addedJavaScript) {
280         throw new UnsupportedOperationException("not supported anymore - use javascript to register your body-event-handler directly");
281     }
282 
283     public void addJavaScriptAtPosition(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler, boolean defer) {
284         try {
285             writeJavaScriptReference(context,getResourceUri(context,resourceHandler),true,defer);
286         } catch (IOException e) {
287             throw new IllegalStateException(e.getMessage());
288         }
289     }
290 
291     public void addJavaScriptAtPositionPlain(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
292         try {
293             writeJavaScriptReference(context,getResourceUri(context,new MyFacesResourceHandler(myfacesCustomComponent, resourceName)),false,false);
294         } catch (IOException e) {
295             throw new IllegalStateException(e.getMessage());
296         }
297     }
298 
299     public void addStyleSheet(FacesContext context, ResourcePosition position, Class myfacesCustomComponent, String resourceName) {
300         try {
301             writeStyleReference(context,getResourceUri(context,new MyFacesResourceHandler(myfacesCustomComponent, resourceName)));
302         } catch (IOException e) {
303             throw new IllegalStateException(e.getMessage());
304         }
305     }
306 
307     public void addStyleSheet(FacesContext context, ResourcePosition position, String uri) {
308         try {
309             writeStyleReference(context,getResourceUri(context,uri));
310         } catch (IOException e) {
311             throw new IllegalStateException(e.getMessage());
312         }
313     }
314 
315     public void addStyleSheet(FacesContext context, ResourcePosition position, ResourceHandler resourceHandler) {
316         try {
317             writeStyleReference(context,getResourceUri(context,resourceHandler));
318         } catch (IOException e) {
319             throw new IllegalStateException(e.getMessage());
320         }
321     }
322 
323     public void addInlineStyleAtPosition(FacesContext context, ResourcePosition position, String inlineStyle) {
324         try {
325             writeInlineStylesheet(context.getResponseWriter(), inlineStyle);
326         } catch (IOException e) {
327             throw new IllegalStateException(e.getMessage());
328         }
329     }
330 
331     public void addInlineScriptAtPosition(FacesContext context, ResourcePosition position, String inlineScript) {
332         try {
333             writeInlineScript(context.getResponseWriter(), inlineScript);
334         } catch (IOException e) {
335             throw new IllegalStateException(e.getMessage());
336         }
337     }
338 
339     public String getResourceUri(FacesContext context, Class myfacesCustomComponent,
340                                  String resource, boolean withContextPath)
341     {
342         return getResourceUri(context,
343                 new MyFacesResourceHandler(myfacesCustomComponent, resource), withContextPath);
344     }
345 
346     public String getResourceUri(FacesContext context, Class myfacesCustomComponent, String resource)
347     {
348         return getResourceUri(context, new MyFacesResourceHandler(myfacesCustomComponent, resource));
349     }
350 
351 
352     /**
353      * Get the Path used to retrieve an resource.
354      */
355     public String getResourceUri(FacesContext context, ResourceHandler resourceHandler)
356     {
357         String uri = resourceHandler.getResourceUri(context);
358         if (uri == null)
359         {
360             return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true);
361         }
362         return getResourceUri(context, resourceHandler.getResourceLoaderClass(), true) + uri;
363     }
364 
365     /**
366      * Get the Path used to retrieve an resource.
367      */
368     public String getResourceUri(FacesContext context, ResourceHandler resourceHandler,
369                                  boolean withContextPath)
370     {
371         String uri = resourceHandler.getResourceUri(context);
372         if (uri == null)
373         {
374             return getResourceUri(context, resourceHandler.getResourceLoaderClass(),
375                     withContextPath);
376         }
377         return getResourceUri(context, resourceHandler.getResourceLoaderClass(), withContextPath)
378                 + uri;
379     }
380 
381     /**
382      * Get the Path used to retrieve an resource.
383      */
384     public String getResourceUri(FacesContext context, String uri)
385     {
386         return getResourceUri(context, uri, true);
387     }
388 
389     /**
390      * Get the Path used to retrieve an resource.
391      */
392     public String getResourceUri(FacesContext context, String uri, boolean withContextPath)
393     {
394         if (withContextPath)
395         {
396             return context.getApplication().getViewHandler().getResourceURL(context, uri);
397         }
398         return uri;
399     }
400 
401     /**
402      * Get the Path used to retrieve an resource.
403      * @param context current faces-context
404      * @param resourceLoader resourceLoader
405      * @param withContextPath use the context-path of the web-app when accessing the resources
406      *
407      * @return the URI of the resource
408      */
409     protected String getResourceUri(FacesContext context, Class resourceLoader,
410                                     boolean withContextPath)
411     {
412         StringBuffer sb = new StringBuffer(200);
413         sb.append(MyfacesConfig.getCurrentInstance(context.getExternalContext()).getResourceVirtualPath());
414         sb.append(PATH_SEPARATOR);
415         sb.append(resourceLoader.getName());
416         sb.append(PATH_SEPARATOR);
417         sb.append(getCacheKey(context));
418         sb.append(PATH_SEPARATOR);
419         return getResourceUri(context, sb.toString(), withContextPath);
420     }
421 
422     /**
423      * Return a value used in the {cacheKey} part of a generated URL for a
424      * resource reference.
425      * <p/>
426      * Caching in browsers normally works by having files served to them
427      * include last-modified and expiry-time http headers. Until the expiry
428      * time is reached, a browser will silently use its cached version. After
429      * the expiry time, it will send a "get if modified since {time}" message,
430      * where {time} is the last-modified header from the version it has cached.
431      * <p/>
432      * Unfortunately this scheme only works well for resources represented as
433      * plain files on disk, where the webserver can easily and efficiently see
434      * the last-modified time of the resource file. When that query has to be
435      * processed by a servlet that doesn't scale well, even when it is possible
436      * to determine the resource's last-modified date from servlet code.
437      * <p/>
438      * Fortunately, for the AddResource class a static resource is only ever
439      * accessed because a URL was embedded by this class in a dynamic page.
440      * This makes it possible to implement caching by instead marking every
441      * resource served with a very long expiry time, but forcing the URL that
442      * points to the resource to change whenever the old cached version becomes
443      * invalid; the browser effectively thinks it is fetching a different
444      * resource that it hasn't seen before. This is implemented by embedding
445      * a "cache key" in the generated URL.
446      * <p/>
447      * Rather than using the actual modification date of a resource as the
448      * cache key, we simply use the webapp deployment time. This means that all
449      * data cached by browsers will become invalid after a webapp deploy (all
450      * the urls to the resources change). It also means that changes that occur
451      * to a resource <i>without</i> a webapp redeploy will not be seen by browsers.
452      *
453      * @param context the current faces-context
454      *
455      * @return the key for caching
456      */
457     protected long getCacheKey(FacesContext context)
458     {
459         // cache key is hold in application scope so it is recreated on redeploying the webapp.
460         Map applicationMap = context.getExternalContext().getApplicationMap();
461         Long cacheKey = (Long) applicationMap.get(RESOURCES_CACHE_KEY);
462         if (cacheKey == null)
463         {
464             cacheKey = new Long(System.currentTimeMillis() / 100000);
465             applicationMap.put(RESOURCES_CACHE_KEY, cacheKey);
466         }
467         return cacheKey.longValue();
468     }
469 
470     public boolean isResourceUri(ServletContext servletContext, HttpServletRequest request)
471     {
472 
473         String path;
474         if (_contextPath != null)
475         {
476             path = _contextPath + getResourceVirtualPath(servletContext);
477         }
478         else
479         {
480             path = getResourceVirtualPath(servletContext);
481         }
482 
483         //fix for TOMAHAWK-660; to be sure this fix is backwards compatible, the
484         //encoded context-path is only used as a first option to check for the prefix
485         //if we're sure this works for all cases, we can directly return the first value
486         //and not double-check.
487         try
488         {
489             if(request.getRequestURI().startsWith(URLEncoder.encode(path,"UTF-8")))
490                 return true;
491         }
492         catch (UnsupportedEncodingException e)
493         {
494             log.error("Unsupported encoding UTF-8 used",e);
495 
496         }
497 
498         return request.getRequestURI().startsWith(path);
499     }
500 
501     private String getResourceVirtualPath(ServletContext servletContext)
502     {
503         if(resourceVirtualPath == null)
504         {
505             resourceVirtualPath = servletContext.getInitParameter(MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH);
506 
507             if(resourceVirtualPath == null)
508             {
509                 resourceVirtualPath = MyfacesConfig.INIT_PARAM_RESOURCE_VIRTUAL_PATH_DEFAULT;
510             }
511         }
512 
513         return resourceVirtualPath;
514     }
515 
516     private Class getClass(String className) throws ClassNotFoundException
517     {
518         Class clazz = ClassUtils.classForName(className);
519         validateResourceLoader(clazz);
520         return clazz;
521     }
522 
523     public void serveResource(ServletContext context, HttpServletRequest request,
524                               HttpServletResponse response) throws IOException
525     {
526         String pathInfo = request.getPathInfo();
527         String uri = request.getContextPath() + request.getServletPath()
528                 + (pathInfo == null ? "" : pathInfo);
529         String classNameStartsAfter = getResourceVirtualPath(context) + '/';
530 
531         int posStartClassName = uri.indexOf(classNameStartsAfter) + classNameStartsAfter.length();
532         int posEndClassName = uri.indexOf(PATH_SEPARATOR, posStartClassName);
533         String className = uri.substring(posStartClassName, posEndClassName);
534         int posEndCacheKey = uri.indexOf(PATH_SEPARATOR, posEndClassName + 1);
535         String resourceUri = null;
536         if (posEndCacheKey + 1 < uri.length())
537         {
538             resourceUri = uri.substring(posEndCacheKey + 1);
539         }
540         try
541         {
542             Class resourceLoader = getClass(className);
543             validateResourceLoader(resourceLoader);
544             ((ResourceLoader) resourceLoader.newInstance()).serveResource(context, request,
545                     response, resourceUri);
546 
547             // Do not call response.flushBuffer buffer here. There is no point, as if there
548             // ever were header data to write, this would fail as we have already written
549             // the response body. The only point would be to flush the output stream, but
550             // that will happen anyway when the servlet container closes the socket.
551             //
552             // In addition, flushing could fail here; it appears that Microsoft IE
553             // hasthe habit of hard-closing its socket as soon as it has received a complete
554             // gif file, rather than letting the server close it. The container will hopefully
555             // silently ignore exceptions on close.
556         }
557         catch (ResourceLoader.ClosedSocketException e)
558         {
559             // The ResourceLoader was unable to send the data because the client closed
560             // the socket on us; just ignore.
561         }
562         catch (ClassNotFoundException e)
563         {
564             log.error("Could not find class for name: " + className, e);
565             response.sendError(HttpServletResponse.SC_NOT_FOUND,
566                     "Could not find resourceloader class for name: " + className);
567         }
568         catch (InstantiationException e)
569         {
570             log.error("Could not instantiate class for name: " + className, e);
571             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
572                     "Could not instantiate resourceloader class for name: " + className);
573         }
574         catch (IllegalAccessException e)
575         {
576             log.error("Could not access class for name: " + className, e);
577             response.sendError(HttpServletResponse.SC_FORBIDDEN,
578                     "Could not access resourceloader class for name: " + className);
579         }
580         catch (Throwable e)
581         {
582             log.error("Error while serving resource: " + resourceUri + ", message : " + e.getMessage(), e);
583             response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
584         }
585     }
586 
587     public void parseResponse(HttpServletRequest request, String bufferedResponse, HttpServletResponse response) throws IOException {
588         throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
589     }
590 
591     public void writeMyFacesJavascriptBeforeBodyEnd(HttpServletRequest request, HttpServletResponse response) throws IOException {
592         throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
593     }
594 
595     public void writeWithFullHeader(HttpServletRequest request, HttpServletResponse response) throws IOException {
596         throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
597     }
598 
599     public void writeResponse(HttpServletRequest request, HttpServletResponse response) throws IOException {
600         throw new UnsupportedOperationException("non-buffering add resource is not buffering.");
601     }
602 
603     public boolean requiresBuffer() {
604         return false;
605     }
606 
607     public void responseStarted() {
608     }
609 
610     public void responseFinished() {
611     }
612 
613     public boolean hasHeaderBeginInfos() {
614         return false;
615     }
616 }