Java tutorial
/** * @Copyright: 2017 smarabbit studio. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @ <kaimohkh@gmail.com> * @ : 2017517 */ package org.massyframework.assembly.base.web; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.net.URLConnection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.massyframework.assembly.ExportServiceReference; import org.massyframework.assembly.ExportServiceRepository; import org.massyframework.assembly.ExportServiceTracker; import org.massyframework.assembly.util.Asserts; import org.massyframework.assembly.web.HttpResource; import org.massyframework.assembly.web.HttpResourceProcessor; import org.massyframework.assembly.web.ServletContextAware; /** * HttpResourceProcessor? * @author huangkaihui * */ class HttpResourceProcessorManagement extends ExportServiceTracker<HttpResourceProcessor> implements HttpResourceProcessor { private static final String EXTENSION_SEPARATOR = "."; private static final String METHOD_GET = "GET"; private static final String LAST_MODIFIED = "Last-Modified"; private static final String IF_MODIFIED_SINCE = "If-Modified-Since"; private static final String IF_NONE_MATCH = "If-None-Match"; private static final String ETAG = "ETag"; private ServletContext servletContext; private Map<String, HttpResourceProcessor> processorMap = new ConcurrentHashMap<String, HttpResourceProcessor>(); /** * @param serviceRepository * @param serviceType * @param filterString */ public HttpResourceProcessorManagement(ExportServiceRepository serviceRepository, ServletContext servletContext) { super(serviceRepository, HttpResourceProcessor.class, null); Asserts.notNull(servletContext, "servletContext cannot be null."); this.servletContext = servletContext; } /* (non-Javadoc) * @see org.massyframework.assembly.web.HttpResourceProcessor#process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, org.massyframework.assembly.web.HttpResource) */ @Override public void process(HttpServletRequest req, HttpServletResponse resp, HttpResource resource) throws ServletException, IOException { String path = req.getPathInfo(); String extensionName = this.parseExtensionName(path); HttpResourceProcessor processor = null; if (extensionName != null) { processor = this.processorMap.get(extensionName); } if (processor != null) { processor.process(req, resp, resource); } else { if (req.getMethod().equals(METHOD_GET)) { this.doGet(req, resp, resource); } else { this.sendError(resp, HttpServletResponse.SC_NOT_FOUND); } } } /** * ???Http Get,??? * @param request * @param response * @param resource */ protected void doGet(HttpServletRequest req, HttpServletResponse resp, HttpResource resource) throws IOException { String path = req.getPathInfo(); if (path != null && path.startsWith("/WEB-INF/")) { //$NON-NLS-1$ resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } if (!path.startsWith("/")) { path = "/" + path; } String resourceName = resource.getName() + path; URL url = resource.getAssemblyClassLoader().getResource(resourceName); if (url != null) { this.writeResource(req, resp, path, url); } else { this.sendError(resp, HttpServletResponse.SC_NOT_FOUND); } } /** * ???Http? * @param req http * @param resp http? * @param resourcePath ? * @param resourceURL ?URL * @throws IOException ?IO */ private void writeResource(final HttpServletRequest req, final HttpServletResponse resp, final String pathInfo, final URL resourceURL) throws IOException { URLConnection connection = resourceURL.openConnection(); long lastModified = connection.getLastModified(); int contentLength = connection.getContentLength(); String etag = null; if (lastModified != -1 && contentLength != -1) etag = "W/\"" + contentLength + "-" + lastModified + "\""; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ // Check for cache revalidation. // We should prefer ETag validation as the guarantees are stronger and all HTTP 1.1 clients should be using it String ifNoneMatch = req.getHeader(IF_NONE_MATCH); if (ifNoneMatch != null && etag != null && ifNoneMatch.indexOf(etag) != -1) { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } long ifModifiedSince = req.getDateHeader(IF_MODIFIED_SINCE); // for purposes of comparison we add 999 to ifModifiedSince since the fidelity // of the IMS header generally doesn't include milli-seconds if (ifModifiedSince > -1 && lastModified > 0 && lastModified <= (ifModifiedSince + 999)) { resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); } // return the full contents regularly if (contentLength != -1) resp.setContentLength(contentLength); String contentType = getServletContext().getMimeType(pathInfo); if (contentType != null) resp.setContentType(contentType); if (lastModified > 0) resp.setDateHeader(LAST_MODIFIED, lastModified); if (etag != null) resp.setHeader(ETAG, etag); if (contentLength != 0) { // open the input stream InputStream is = null; try { is = connection.getInputStream(); // write the resource try { OutputStream os = resp.getOutputStream(); int writtenContentLength = writeResourceToOutputStream(is, os); if (contentLength == -1 || contentLength != writtenContentLength) resp.setContentLength(writtenContentLength); } catch (IllegalStateException e) { // can occur if the response output is already open as a Writer Writer writer = resp.getWriter(); writeResourceToWriter(is, writer); // Since ContentLength is a measure of the number of bytes contained in the body // of a message when we use a Writer we lose control of the exact byte count and // defer the problem to the Servlet Engine's Writer implementation. } } catch (FileNotFoundException e) { // FileNotFoundException may indicate the following scenarios // - url is a directory // - url is not accessible sendError(resp, HttpServletResponse.SC_FORBIDDEN); } catch (SecurityException e) { // SecurityException may indicate the following scenarios // - url is not accessible sendError(resp, HttpServletResponse.SC_FORBIDDEN); } finally { if (is != null) try { is.close(); } catch (IOException e) { // ignore } } } } /** * ?? * @param is * @param os * @return * @throws IOException */ private int writeResourceToOutputStream(InputStream is, OutputStream os) throws IOException { byte[] buffer = new byte[8192]; int bytesRead = is.read(buffer); int writtenContentLength = 0; while (bytesRead != -1) { os.write(buffer, 0, bytesRead); writtenContentLength += bytesRead; bytesRead = is.read(buffer); } return writtenContentLength; } /** * Writer * @param is * @param writer * @throws IOException */ private void writeResourceToWriter(InputStream is, Writer writer) throws IOException { Reader reader = new InputStreamReader(is); try { char[] buffer = new char[8192]; int charsRead = reader.read(buffer); while (charsRead != -1) { writer.write(buffer, 0, charsRead); charsRead = reader.read(buffer); } } finally { if (reader != null) { reader.close(); // will also close input stream } } } /** * ?? * @param resp * @param sc * @throws IOException */ private void sendError(final HttpServletResponse resp, int sc) throws IOException { try { // we need to reset headers for 302 and 403 resp.reset(); resp.sendError(sc); } catch (IllegalStateException e) { // this could happen if the response has already been committed } } protected ServletContext getServletContext() { return this.servletContext; } /* (non-Javadoc) * @see org.massyframework.assembly.web.HttpResourceProcessor#getExtensionName() */ @Override public String[] getExtensionNames() { return null; } protected String parseExtensionName(String path) { int index = StringUtils.lastIndexOf(path, AbstractHttpService.PATH_SEPARATOR); int index_ext = StringUtils.lastIndexOf(path, EXTENSION_SEPARATOR); if (index_ext > index) { return StringUtils.substring(path, index_ext); } return null; } /* (non-Javadoc) * @see org.massyframework.assembly.ExportServiceTracker#addService(org.massyframework.assembly.ExportServiceReference, java.lang.Object) */ @Override protected void addService(ExportServiceReference<HttpResourceProcessor> reference, HttpResourceProcessor service) { super.addService(reference, service); String[] extensionNames = service.getExtensionNames(); for (String extensionName : extensionNames) { if (extensionName != null) { if (extensionName.startsWith("*")) { extensionName = StringUtils.substring(extensionName, 1); } if (!extensionName.startsWith(".")) { extensionName = "." + extensionName; } ServletContextAware.maybeToBind(service, servletContext); processorMap.putIfAbsent(extensionName, service); } } } /* (non-Javadoc) * @see org.massyframework.assembly.ExportServiceTracker#removeService(org.massyframework.assembly.ExportServiceReference, java.lang.Object) */ @Override protected void removeService(ExportServiceReference<HttpResourceProcessor> reference, HttpResourceProcessor service) { String[] extensionNames = service.getExtensionNames(); for (String extensionName : extensionNames) { if (extensionName != null) { if (extensionName.startsWith("*")) { extensionName = StringUtils.substring(extensionName, 1); } if (!extensionName.startsWith(".")) { extensionName = "." + extensionName; } processorMap.remove(extensionName, service); } } super.removeService(reference, service); } }