diff options
Diffstat (limited to '')
51 files changed, 10694 insertions, 1 deletions
diff --git a/src/java/nginx/unit/Context.java b/src/java/nginx/unit/Context.java new file mode 100644 index 00000000..643a336b --- /dev/null +++ b/src/java/nginx/unit/Context.java @@ -0,0 +1,3502 @@ +package nginx.unit; + +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ClassInfoList; +import io.github.classgraph.ScanResult; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +import java.lang.ClassLoader; +import java.lang.ClassNotFoundException; +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.reflect.Constructor; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; + +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.UUID; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.zip.ZipEntry; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.FilterRegistration; +import javax.servlet.MultipartConfigElement; +import javax.servlet.Registration; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextAttributeEvent; +import javax.servlet.ServletContextAttributeListener; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRegistration; +import javax.servlet.ServletResponse; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletRequestEvent; +import javax.servlet.ServletRequestListener; +import javax.servlet.ServletSecurityElement; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.annotation.HandlesTypes; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.annotation.WebFilter; +import javax.servlet.annotation.WebListener; +import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.descriptor.JspPropertyGroupDescriptor; +import javax.servlet.descriptor.TaglibDescriptor; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionIdListener; +import javax.servlet.http.HttpSessionListener; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.eclipse.jetty.http.MimeTypes; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import org.apache.jasper.servlet.JspServlet; +import org.apache.jasper.servlet.JasperInitializer; + + +public class Context implements ServletContext, InitParams +{ + public final static int SERVLET_MAJOR_VERSION = 3; + public final static int SERVLET_MINOR_VERSION = 1; + + private String context_path_ = ""; + private String server_info_ = "unit"; + private String app_version_ = ""; + private MimeTypes mime_types_; + private boolean metadata_complete_ = false; + private boolean welcome_files_list_found_ = false; + private boolean ctx_initialized_ = false; + + private ClassLoader loader_; + private File webapp_; + private File extracted_dir_; + private File temp_dir_; + + private final Map<String, String> init_params_ = new HashMap<>(); + private final Map<String, Object> attributes_ = new HashMap<>(); + + private final Map<String, URLPattern> parsed_patterns_ = new HashMap<>(); + + private final List<FilterReg> filters_ = new ArrayList<>(); + private final Map<String, FilterReg> name2filter_ = new HashMap<>(); + private final List<FilterMap> filter_maps_ = new ArrayList<>(); + + private final List<ServletReg> servlets_ = new ArrayList<>(); + private final Map<String, ServletReg> name2servlet_ = new HashMap<>(); + private final Map<String, ServletReg> pattern2servlet_ = new HashMap<>(); + private final Map<String, ServletReg> exact2servlet_ = new HashMap<>(); + private final List<PrefixPattern> prefix_patterns_ = new ArrayList<>(); + private final Map<String, ServletReg> suffix2servlet_ = new HashMap<>(); + private ServletReg default_servlet_; + private ServletReg system_default_servlet_; + + private final List<String> welcome_files_ = new ArrayList<>(); + + private final Map<String, String> exception2location_ = new HashMap<>(); + private final Map<Integer, String> error2location_ = new HashMap<>(); + + public static final Class<?>[] LISTENER_TYPES = new Class[] { + ServletContextListener.class, + ServletContextAttributeListener.class, + ServletRequestListener.class, + ServletRequestAttributeListener.class, + HttpSessionAttributeListener.class, + HttpSessionIdListener.class, + HttpSessionListener.class + }; + + private final List<String> pending_listener_classnames_ = new ArrayList<>(); + private final Set<String> listener_classnames_ = new HashSet<>(); + + private final List<ServletContextListener> ctx_listeners_ = new ArrayList<>(); + private final List<ServletContextListener> destroy_listeners_ = new ArrayList<>(); + private final List<ServletContextAttributeListener> ctx_attr_listeners_ = new ArrayList<>(); + private final List<ServletRequestListener> req_init_listeners_ = new ArrayList<>(); + private final List<ServletRequestListener> req_destroy_listeners_ = new ArrayList<>(); + private final List<ServletRequestAttributeListener> req_attr_listeners_ = new ArrayList<>(); + + private ServletRequestAttributeListener req_attr_proxy_ = null; + + private final List<HttpSessionAttributeListener> sess_attr_listeners_ = new ArrayList<>(); + private final List<HttpSessionIdListener> sess_id_listeners_ = new ArrayList<>(); + private final List<HttpSessionListener> sess_listeners_ = new ArrayList<>(); + + private HttpSessionAttributeListener sess_attr_proxy_ = null; + + private final SessionCookieConfig session_cookie_config_ = new UnitSessionCookieConfig(); + private final Set<SessionTrackingMode> default_session_tracking_modes_ = new HashSet<>(); + private Set<SessionTrackingMode> session_tracking_modes_ = default_session_tracking_modes_; + private int session_timeout_ = 60; + + private final Map<String, Session> sessions_ = new HashMap<>(); + + private static final String WEB_INF = "WEB-INF/"; + private static final String WEB_INF_CLASSES = WEB_INF + "classes/"; + private static final String WEB_INF_LIB = WEB_INF + "lib/"; + + private class PrefixPattern implements Comparable<PrefixPattern> + { + public final String pattern; + public final ServletReg servlet; + + public PrefixPattern(String p, ServletReg s) + { + pattern = p; + servlet = s; + } + + public boolean match(String url) + { + return url.startsWith(pattern) && ( + url.length() == pattern.length() + || url.charAt(pattern.length()) == '/'); + } + + @Override + public int compareTo(PrefixPattern p) + { + return p.pattern.length() - pattern.length(); + } + } + + private class StaticServlet extends HttpServlet + { + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + doGet(request, response); + } + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException + { + String path = null; + + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + path = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + } + + if (path == null) { + path = request.getServletPath(); + } + + /* + 10.6 Web Application Archive File + ... + This directory [META-INF] must not be directly served as + content by the container in response to a Web client's request, + though its contents are visible to servlet code via the + getResource and getResourceAsStream calls on the + ServletContext. Also, any requests to access the resources in + META-INF directory must be returned with a SC_NOT_FOUND(404) + response. + */ + if (request.getDispatcherType() == DispatcherType.REQUEST + && (path.equals("/WEB-INF") || path.startsWith("/WEB-INF/") + || path.equals("/META-INF") || path.startsWith("/META-INF/"))) + { + response.sendError(response.SC_NOT_FOUND); + return; + } + + if (path.startsWith("/")) { + path = path.substring(1); + } + + File f = new File(webapp_, path); + if (!f.exists()) { + if (request.getDispatcherType() == DispatcherType.INCLUDE) { + /* + 9.3 The Include Method + ... + If the default servlet is the target of a + RequestDispatch.include() and the requested resource + does not exist, then the default servlet MUST throw + FileNotFoundException. + */ + + throw new FileNotFoundException(); + } + + response.sendError(response.SC_NOT_FOUND); + return; + } + + long ims = request.getDateHeader("If-Modified-Since"); + long lm = f.lastModified(); + + if (lm < ims) { + response.sendError(response.SC_NOT_MODIFIED); + return; + } + + response.setDateHeader("Last-Modified", f.lastModified()); + + if (f.isDirectory()) { + String url = request.getRequestURL().toString(); + if (!url.endsWith("/")) { + response.setHeader("Location", url + "/"); + response.sendError(response.SC_FOUND); + return; + } + + String[] ls = f.list(); + + PrintWriter writer = response.getWriter(); + + for (String n : ls) { + writer.println("<a href=\"" + n + "\">" + n + "</a><br>"); + } + + writer.close(); + + } else { + response.setContentLengthLong(f.length()); + + InputStream is = new FileInputStream(f); + byte[] buffer = new byte[response.getBufferSize()]; + ServletOutputStream os = response.getOutputStream(); + while (true) { + int read = is.read(buffer); + if (read == -1) { + break; + } + os.write(buffer, 0, read); + } + + os.close(); + } + } + } + + public static Context start(String webapp, URL[] classpaths) + throws Exception + { + Context ctx = new Context(); + + ctx.loadApp(webapp, classpaths); + ctx.initialized(); + + return ctx; + } + + public Context() + { + default_session_tracking_modes_.add(SessionTrackingMode.COOKIE); + + context_path_ = System.getProperty("nginx.unit.context.path", "").trim(); + + if (context_path_.endsWith("/")) { + context_path_ = context_path_.substring(0, context_path_.length() - 1); + } + + if (!context_path_.isEmpty() && !context_path_.startsWith("/")) { + context_path_ = "/" + context_path_; + } + + if (context_path_.isEmpty()) { + session_cookie_config_.setPath("/"); + } else { + session_cookie_config_.setPath(context_path_); + } + } + + public void loadApp(String webapp, URL[] classpaths) + throws Exception + { + File root = new File(webapp); + if (!root.exists()) { + throw new FileNotFoundException( + "Unable to determine code source archive from " + root); + } + + ArrayList<URL> url_list = new ArrayList<>(); + + for (URL u : classpaths) { + url_list.add(u); + } + + if (!root.isDirectory()) { + root = extractWar(root); + extracted_dir_ = root; + } + + webapp_ = root; + + Path tmpDir = Files.createTempDirectory("webapp"); + temp_dir_ = tmpDir.toFile(); + setAttribute(ServletContext.TEMPDIR, temp_dir_); + + File web_inf_classes = new File(root, WEB_INF_CLASSES); + if (web_inf_classes.exists() && web_inf_classes.isDirectory()) { + url_list.add(new URL("file:" + root.getAbsolutePath() + "/" + WEB_INF_CLASSES)); + } + + File lib = new File(root, WEB_INF_LIB); + File[] libs = lib.listFiles(); + + if (libs != null) { + for (File l : libs) { + url_list.add(new URL("file:" + l.getAbsolutePath())); + } + } + + URL[] urls = new URL[url_list.size()]; + + for (int i = 0; i < url_list.size(); i++) { + urls[i] = url_list.get(i); + trace("archives: " + urls[i]); + } + + String custom_listener = System.getProperty("nginx.unit.context.listener", "").trim(); + if (!custom_listener.isEmpty()) { + pending_listener_classnames_.add(custom_listener); + } + + processWebXml(root); + + loader_ = new AppClassLoader(urls, + Context.class.getClassLoader().getParent()); + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (String listener_classname : pending_listener_classnames_) { + addListener(listener_classname); + } + + ScanResult scan_res = null; + + if (!metadata_complete_) { + ClassGraph classgraph = new ClassGraph() + //.verbose() + .overrideClassLoaders(loader_) + .ignoreParentClassLoaders() + .enableClassInfo() + .enableAnnotationInfo() + //.enableSystemPackages() + .whitelistModules("javax.*") + //.enableAllInfo() + ; + + String verbose = System.getProperty("nginx.unit.context.classgraph.verbose", "").trim(); + + if (verbose.equals("true")) { + classgraph.verbose(); + } + + scan_res = classgraph.scan(); + + loadInitializers(scan_res); + } + + if (!metadata_complete_) { + scanClasses(scan_res); + } + + /* + 8.1.6 Other annotations / conventions + ... + By default all applications will have index.htm(l) and index.jsp + in the list of welcome-file-list. The descriptor may to be used + to override these default settings. + */ + if (!welcome_files_list_found_) { + welcome_files_.add("index.htm"); + welcome_files_.add("index.html"); + welcome_files_.add("index.jsp"); + } + + ServletReg jsp_servlet = name2servlet_.get("jsp"); + if (jsp_servlet == null) { + jsp_servlet = new ServletReg("jsp", JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + servlets_.add(jsp_servlet); + name2servlet_.put("jsp", jsp_servlet); + } + + if (jsp_servlet.getClassName() == null) { + jsp_servlet.setClass(JspServlet.class); + jsp_servlet.system_jsp_servlet_ = true; + } + + if (jsp_servlet.patterns_.isEmpty()) { + parseURLPattern("*.jsp", jsp_servlet); + parseURLPattern("*.jspx", jsp_servlet); + } + + ServletReg def_servlet = name2servlet_.get("default"); + if (def_servlet == null) { + def_servlet = new ServletReg("default", new StaticServlet()); + def_servlet.servlet_ = new StaticServlet(); + servlets_.add(def_servlet); + name2servlet_.put("default", def_servlet); + } + + if (def_servlet.getClassName() == null) { + def_servlet.setClass(StaticServlet.class); + def_servlet.servlet_ = new StaticServlet(); + } + + system_default_servlet_ = def_servlet; + + for (PrefixPattern p : prefix_patterns_) { + /* + Optimization: add prefix patterns to exact2servlet_ map. + This should not affect matching result because full path + is the longest matched prefix. + */ + if (!exact2servlet_.containsKey(p.pattern)) { + trace("adding prefix pattern " + p.pattern + " to exact patterns map"); + exact2servlet_.put(p.pattern, p.servlet); + } + } + + Collections.sort(prefix_patterns_); + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private static class AppClassLoader extends URLClassLoader + { + static { + ClassLoader.registerAsParallelCapable(); + } + + private final static String[] system_prefix = + { + "java/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "javax/", // Java SE classes (per servlet spec v2.5 / SRV.9.7.2) + "org/w3c/", // needed by javax.xml + "org/xml/", // needed by javax.xml + }; + + private ClassLoader system_loader; + + public AppClassLoader(URL[] urls, ClassLoader parent) + { + super(urls, parent); + + ClassLoader j = String.class.getClassLoader(); + if (j == null) { + j = getSystemClassLoader(); + while (j.getParent() != null) { + j = j.getParent(); + } + } + system_loader = j; + } + + private boolean isSystemPath(String path) + { + int i = Arrays.binarySearch(system_prefix, path); + + if (i >= 0) { + return true; + } + + i = -i - 1; + + if (i > 0) { + return path.startsWith(system_prefix[i - 1]); + } + + return false; + } + + @Override + public URL getResource(String name) + { + URL res; + + String s = "getResource: " + name; + trace(0, s, s.length()); + + /* + This is a required for compatibility with Tomcat which + stores all resources prefixed with '/' and application code + may try to get resource with leading '/' (like Jira). Jetty + also has such workaround in WebAppClassLoader.getResource(). + */ + if (name.startsWith("/")) { + name = name.substring(1); + } + + if (isSystemPath(name)) { + return super.getResource(name); + } + + res = system_loader.getResource(name); + if (res != null) { + return res; + } + + res = findResource(name); + if (res != null) { + return res; + } + + return super.getResource(name); + } + + @Override + protected Class<?> loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + synchronized (this) { + Class<?> res = findLoadedClass(name); + if (res != null) { + return res; + } + + try { + res = system_loader.loadClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } catch (ClassNotFoundException e) { + } + + String path = name.replace('.', '/').concat(".class"); + + if (isSystemPath(path)) { + return super.loadClass(name, resolve); + } + + URL url = findResource(path); + + if (url != null) { + res = super.findClass(name); + + if (resolve) { + resolveClass(res); + } + + return res; + } + + return super.loadClass(name, resolve); + } + + } + } + + private File extractWar(File war) throws IOException + { + Path tmpDir = Files.createTempDirectory("webapp"); + + JarFile jf = new JarFile(war); + + for (Enumeration<JarEntry> en = jf.entries(); en.hasMoreElements();) { + JarEntry e = en.nextElement(); + long mod_time = e.getTime(); + Path ep = tmpDir.resolve(e.getName()); + Path p; + if (e.isDirectory()) { + p = ep; + } else { + p = ep.getParent(); + } + + if (!p.toFile().isDirectory()) { + Files.createDirectories(p); + } + + if (!e.isDirectory()) { + Files.copy(jf.getInputStream(e), ep, + StandardCopyOption.REPLACE_EXISTING); + } + + if (mod_time > 0) { + ep.toFile().setLastModified(mod_time); + } + } + + return tmpDir.toFile(); + } + + private class CtxFilterChain implements FilterChain + { + private int filter_index_ = 0; + private final ServletReg servlet_; + private final List<FilterReg> filters_; + + CtxFilterChain(ServletReg servlet, String path, DispatcherType dtype) + { + servlet_ = servlet; + + List<FilterReg> filters = new ArrayList<>(); + + for (FilterMap m : filter_maps_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + if (m.pattern_.match(path)) { + filters.add(m.filter_); + + trace("add filter (matched): " + m.filter_.getName()); + } + } + + for (FilterMap m : servlet.filters_) { + if (filters.indexOf(m.filter_) != -1) { + continue; + } + + if (!m.dtypes_.contains(dtype)) { + continue; + } + + filters.add(m.filter_); + + trace("add filter (servlet): " + m.filter_.getName()); + } + + filters_ = filters; + } + + @Override + public void doFilter (ServletRequest request, ServletResponse response) + throws IOException, ServletException + { + if (filter_index_ < filters_.size()) { + filters_.get(filter_index_++).filter_.doFilter(request, response, this); + + return; + } + + servlet_.service(request, response); + } + } + + private ServletReg findServlet(String path, DynamicPathRequest req) + { + /* + 12.1 Use of URL Paths + ... + 1. The container will try to find an exact match of the path of the + request to the path of the servlet. A successful match selects + the servlet. + */ + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + return servlet; + } + + /* + 2. The container will recursively try to match the longest + path-prefix. This is done by stepping down the path tree a + directory at a time, using the '/' character as a path separator. + The longest match determines the servlet selected. + */ + for (PrefixPattern p : prefix_patterns_) { + if (p.match(path)) { + trace("findServlet: '" + path + "' matched prefix pattern '" + p.pattern + "'"); + if (p.pattern.length() == path.length()) { + log("findServlet: WARNING: it is expected '" + path + "' exactly matches " + p.pattern); + req.setServletPath(path, p.pattern, null); + } else { + req.setServletPath(path, p.pattern, path.substring(p.pattern.length())); + } + return p.servlet; + } + } + + /* + 3. If the last segment in the URL path contains an extension + (e.g. .jsp), the servlet container will try to match a servlet + that handles requests for the extension. An extension is defined + as the part of the last segment after the last '.' character. + */ + int suffix_start = path.lastIndexOf('.'); + if (suffix_start != -1) { + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet != null) { + trace("findServlet: '" + path + "' matched suffix pattern"); + req.setServletPath(path, null); + return servlet; + } + } + + /* + 4. If neither of the previous three rules result in a servlet match, + the container will attempt to serve content appropriate for the + resource requested. If a "default" servlet is defined for the + application, it will be used. ... + */ + if (default_servlet_ != null) { + trace("findServlet: '" + path + "' matched default servlet"); + req.setServletPath(path, null); + return default_servlet_; + } + + trace("findServlet: '" + path + "' no servlet found"); + + /* + 10.10 Welcome Files + ... + If a Web container receives a valid partial request, the Web + container must examine the welcome file list defined in the + deployment descriptor. + ... + */ + if (path.endsWith("/")) { + + /* + The Web server must append each welcome file in the order + specified in the deployment descriptor to the partial request + and check whether a static resource in the WAR is mapped to + that request URI. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + File f = new File(webapp_, wpath.substring(1)); + if (!f.exists()) { + continue; + } + + trace("findServlet: '" + path + "' found static welcome " + + "file '" + wf + "'"); + + /* + Even if static file found, we should try to find matching + servlet for JSP serving etc. + */ + servlet = findWelcomeServlet(wpath, true, req); + if (servlet != null) { + return servlet; + } + + req.setServletPath(wpath, null); + + return system_default_servlet_; + } + + /* + If no match is found, the Web server MUST again append each + welcome file in the order specified in the deployment + descriptor to the partial request and check if a servlet is + mapped to that request URI. The Web container must send the + request to the first resource in the WAR that matches. + */ + for (String wf : welcome_files_) { + String wpath = path + wf; + + servlet = findWelcomeServlet(wpath, false, req); + if (servlet != null) { + return servlet; + } + } + } + + trace("findServlet: '" + path + "' fallback to system default servlet"); + req.setServletPath(path, null); + + return system_default_servlet_; + } + + private ServletReg findWelcomeServlet(String path, boolean exists, + DynamicPathRequest req) + { + ServletReg servlet = exact2servlet_.get(path); + if (servlet != null) { + trace("findWelcomeServlet: '" + path + "' exact matched pattern"); + req.setServletPath(path, null); + + return servlet; + } + + int suffix_start = path.lastIndexOf('.'); + if (suffix_start == -1) { + return null; + } + + String suffix = path.substring(suffix_start); + servlet = suffix2servlet_.get(suffix); + if (servlet == null) { + return null; + } + + trace("findWelcomeServlet: '" + path + "' matched suffix pattern"); + + /* + If we want to show the directory content when + index.jsp is absent, then we have to check file + presence here. Otherwise user will get 404. + */ + + if (servlet.system_jsp_servlet_ && !exists) { + trace("findWelcomeServlet: '" + path + "' not exists"); + return null; + } + + req.setServletPath(path, null); + + return servlet; + } + + public void service(Request req, Response resp) + throws ServletException, IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + ServletRequestEvent sre = null; + + try { + if (!req_init_listeners_.isEmpty()) { + sre = new ServletRequestEvent(this, req); + + for (ServletRequestListener l : req_init_listeners_) { + l.requestInitialized(sre); + } + } + + URI uri = new URI(req.getRequestURI()); + String path = uri.getPath(); + + if (!path.startsWith(context_path_) + || (path.length() > context_path_.length() + && path.charAt(context_path_.length()) != '/')) + { + trace("service: '" + path + "' not started with '" + context_path_ + "'"); + + resp.sendError(resp.SC_NOT_FOUND); + return; + } + + if (path.equals(context_path_)) { + String url = req.getRequestURL().toString(); + if (!url.endsWith("/")) { + resp.setHeader("Location", url + "/"); + resp.sendError(resp.SC_FOUND); + return; + } + } + + path = path.substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.REQUEST); + + fc.doFilter(req, resp); + + Object code = req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + if (code != null && code instanceof Integer) { + handleStatusCode((Integer) code, req, resp); + } + } catch (Throwable e) { + trace("service: caught " + e); + + try { + if (!resp.isCommitted() && !exception2location_.isEmpty()) { + handleException(e, req, resp); + } + + if (!resp.isCommitted()) { + resp.reset(); + resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR); + resp.setContentType("text/plain"); + + PrintWriter w = resp.getWriter(); + w.println("Unhandled exception: " + e); + e.printStackTrace(w); + + w.close(); + } + } finally { + throw new ServletException(e); + } + } finally { + resp.flushBuffer(); + + try { + if (!req_destroy_listeners_.isEmpty()) { + for (ServletRequestListener l : req_destroy_listeners_) { + l.requestDestroyed(sre); + } + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + } + + private void handleException(Throwable e, Request req, Response resp) + throws ServletException, IOException + { + String location; + + Class<?> cls = e.getClass(); + while (cls != null && !cls.equals(Throwable.class)) { + location = exception2location_.get(cls.getName()); + + if (location != null) { + trace("Exception " + e + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION, e); + req.setAttribute_(RequestDispatcher.ERROR_EXCEPTION_TYPE, e.getClass()); + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + req.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, resp.SC_INTERNAL_SERVER_ERROR); + + handleError(location, req, resp); + + return; + } + + cls = cls.getSuperclass(); + } + + if (ServletException.class.isAssignableFrom(e.getClass())) { + ServletException se = (ServletException) e; + + handleException(se.getRootCause(), req, resp); + } + } + + private void handleStatusCode(int code, Request req, Response resp) + throws ServletException, IOException + { + String location; + + location = error2location_.get(code); + + if (location != null) { + trace("Status " + code + " matched. Error page location: " + location); + + req.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, req.getRequestURI()); + + handleError(location, req, resp); + } + } + + public void handleError(String location, Request req, Response resp) + throws ServletException, IOException + { + try { + log("handleError: " + location); + + String filter_path = req.getFilterPath(); + String servlet_path = req.getServletPath(); + String path_info = req.getPathInfo(); + String req_uri = req.getRequestURI(); + DispatcherType dtype = req.getDispatcherType(); + + URI uri; + + if (location.startsWith("/")) { + uri = new URI(context_path_ + location); + } else { + uri = new URI(req_uri).resolve(location); + } + + req.setRequestURI(uri.getRawPath()); + req.setDispatcherType(DispatcherType.ERROR); + + String path = uri.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.ERROR); + + fc.doFilter(req, resp); + + req.setServletPath(filter_path, servlet_path, path_info); + req.setRequestURI(req_uri); + req.setDispatcherType(dtype); + } catch (URISyntaxException e) { + throw new ServletException(e); + } + } + + private void processWebXml(File root) throws Exception + { + if (root.isDirectory()) { + File web_xml = new File(root, "WEB-INF/web.xml"); + if (web_xml.exists()) { + trace("start: web.xml file found"); + + InputStream is = new FileInputStream(web_xml); + + processWebXml(is); + + is.close(); + } + } else { + JarFile jf = new JarFile(root); + ZipEntry ze = jf.getEntry("WEB-INF/web.xml"); + + if (ze == null) { + trace("start: web.xml entry NOT found"); + } else { + trace("start: web.xml entry found"); + + processWebXml(jf.getInputStream(ze)); + } + + jf.close(); + } + } + + private void processWebXml(InputStream is) + throws ParserConfigurationException, SAXException, IOException + { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + + Document doc = builder.parse(is); + + Element doc_elem = doc.getDocumentElement(); + String doc_elem_name = doc_elem.getNodeName(); + if (!doc_elem_name.equals("web-app")) { + throw new RuntimeException("Invalid web.xml: 'web-app' element expected, not '" + doc_elem_name + "'"); + } + + metadata_complete_ = doc_elem.getAttribute("metadata-complete").equals("true"); + app_version_ = doc_elem.getAttribute("version"); + + NodeList welcome_file_lists = doc_elem.getElementsByTagName("welcome-file-list"); + + if (welcome_file_lists.getLength() > 0) { + welcome_files_list_found_ = true; + } + + for (int i = 0; i < welcome_file_lists.getLength(); i++) { + Element list_el = (Element) welcome_file_lists.item(i); + NodeList files = list_el.getElementsByTagName("welcome-file"); + for (int j = 0; j < files.getLength(); j++) { + Node node = files.item(j); + String wf = node.getTextContent().trim(); + + /* + 10.10 Welcome Files + ... + The welcome file list is an ordered list of partial URLs + with no trailing or leading /. + */ + + if (wf.startsWith("/") || wf.endsWith("/")) { + log("invalid welcome file: " + wf); + continue; + } + + welcome_files_.add(wf); + } + } + + NodeList context_params = doc_elem.getElementsByTagName("context-param"); + for (int i = 0; i < context_params.getLength(); i++) { + processXmlInitParam(this, (Element) context_params.item(i)); + } + + NodeList filters = doc_elem.getElementsByTagName("filter"); + + for (int i = 0; i < filters.getLength(); i++) { + Element filter_el = (Element) filters.item(i); + NodeList names = filter_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = new FilterReg(filter_name); + + NodeList child_nodes = filter_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("filter-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + } + + filters_.add(reg); + name2filter_.put(filter_name, reg); + } + + NodeList filter_mappings = doc_elem.getElementsByTagName("filter-mapping"); + + for(int i = 0; i < filter_mappings.getLength(); i++) { + Element mapping_el = (Element) filter_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("filter-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'filter-name' tag not found"); + } + + String filter_name = names.item(0).getTextContent().trim(); + trace("filter-name=" + filter_name); + + FilterReg reg = name2filter_.get(filter_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: filter '" + filter_name + "' not found"); + } + + EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class); + NodeList dispatchers = mapping_el.getElementsByTagName("dispatcher"); + for (int j = 0; j < dispatchers.getLength(); j++) { + Node child_node = dispatchers.item(j); + dtypes.add(DispatcherType.valueOf(child_node.getTextContent().trim())); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + NodeList child_nodes = mapping_el.getChildNodes(); + for (int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + reg.addMappingForUrlPatterns(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("servlet-name")) { + reg.addMappingForServletNames(dtypes, match_after, child_node.getTextContent().trim()); + continue; + } + } + } + + NodeList servlets = doc_elem.getElementsByTagName("servlet"); + + for (int i = 0; i < servlets.getLength(); i++) { + Element servlet_el = (Element) servlets.item(i); + NodeList names = servlet_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = new ServletReg(servlet_name); + + NodeList child_nodes = servlet_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("servlet-class")) { + reg.setClassName(child_node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("async-supported")) { + reg.setAsyncSupported(child_node.getTextContent().trim() + .equals("true")); + continue; + } + + if (tag_name.equals("init-param")) { + processXmlInitParam(reg, (Element) child_node); + continue; + } + + if (tag_name.equals("load-on-startup")) { + reg.setLoadOnStartup(Integer.parseInt(child_node.getTextContent().trim())); + continue; + } + } + + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } + + NodeList servlet_mappings = doc_elem.getElementsByTagName("servlet-mapping"); + + for(int i = 0; i < servlet_mappings.getLength(); i++) { + Element mapping_el = (Element) servlet_mappings.item(i); + NodeList names = mapping_el.getElementsByTagName("servlet-name"); + if (names == null || names.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'servlet-name' tag not found"); + } + + String servlet_name = names.item(0).getTextContent().trim(); + trace("servlet-name=" + servlet_name); + + ServletReg reg = name2servlet_.get(servlet_name); + if (reg == null) { + throw new RuntimeException("Invalid web.xml: servlet '" + servlet_name + "' not found"); + } + + NodeList child_nodes = mapping_el.getElementsByTagName("url-pattern"); + String patterns[] = new String[child_nodes.getLength()]; + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + patterns[j] = child_node.getTextContent().trim(); + } + + reg.addMapping(patterns); + } + + NodeList listeners = doc_elem.getElementsByTagName("listener"); + + for (int i = 0; i < listeners.getLength(); i++) { + Element listener_el = (Element) listeners.item(i); + NodeList classes = listener_el.getElementsByTagName("listener-class"); + if (classes == null || classes.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'listener-class' tag not found"); + } + + String class_name = classes.item(0).getTextContent().trim(); + trace("listener-class=" + class_name); + + pending_listener_classnames_.add(class_name); + } + + NodeList error_pages = doc_elem.getElementsByTagName("error-page"); + + for (int i = 0; i < error_pages.getLength(); i++) { + Element error_page_el = (Element) error_pages.item(i); + NodeList locations = error_page_el.getElementsByTagName("location"); + if (locations == null || locations.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'location' tag not found"); + } + + String location = locations.item(0).getTextContent().trim(); + + NodeList child_nodes = error_page_el.getChildNodes(); + for(int j = 0; j < child_nodes.getLength(); j++) { + Node child_node = child_nodes.item(j); + String tag_name = child_node.getNodeName(); + + if (tag_name.equals("exception-type")) { + String ex = child_node.getTextContent().trim(); + + exception2location_.put(ex, location); + trace("error-page: exception " + ex + " -> " + location); + continue; + } + + if (tag_name.equals("error-code")) { + Integer code = Integer.parseInt(child_node.getTextContent().trim()); + + error2location_.put(code, location); + trace("error-page: code " + code + " -> " + location); + continue; + } + } + } + + NodeList session_config = doc_elem.getElementsByTagName("session-config"); + + for (int i = 0; i < session_config.getLength(); i++) { + Element session_config_el = (Element) session_config.item(i); + NodeList session_timeout = session_config_el.getElementsByTagName("session-timeout"); + if (session_timeout != null) { + String timeout = session_timeout.item(0).getTextContent().trim(); + + trace("session_timeout: " + timeout); + session_timeout_ = Integer.parseInt(timeout); + break; + } + } + + NodeList jsp_configs = doc_elem.getElementsByTagName("jsp-config"); + + for (int i = 0; i < jsp_configs.getLength(); i++) { + Element jsp_config_el = (Element) jsp_configs.item(i); + + NodeList jsp_nodes = jsp_config_el.getChildNodes(); + + for(int j = 0; j < jsp_nodes.getLength(); j++) { + Node jsp_node = jsp_nodes.item(j); + String tag_name = jsp_node.getNodeName(); + + if (tag_name.equals("taglib")) { + NodeList tl_nodes = ((Element) jsp_node).getChildNodes(); + Taglib tl = new Taglib(tl_nodes); + + trace("add taglib"); + + taglibs_.add(tl); + continue; + } + + if (tag_name.equals("jsp-property-group")) { + NodeList jpg_nodes = ((Element) jsp_node).getChildNodes(); + JspPropertyGroup conf = new JspPropertyGroup(jpg_nodes); + + trace("add prop group"); + + prop_groups_.add(conf); + continue; + } + } + } + } + + private static int compareVersion(String ver1, String ver2) + { + String[] varr1 = ver1.split("\\."); + String[] varr2 = ver2.split("\\."); + + int max_len = varr1.length > varr2.length ? varr1.length : varr2.length; + for (int i = 0; i < max_len; i++) { + int l = i < varr1.length ? Integer.parseInt(varr1[i]) : 0; + int r = i < varr2.length ? Integer.parseInt(varr2[i]) : 0; + + int res = l - r; + + if (res != 0) { + return res; + } + } + + return 0; + } + + private void processXmlInitParam(InitParams params, Element elem) + throws RuntimeException + { + NodeList n = elem.getElementsByTagName("param-name"); + if (n == null || n.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-name' tag not found"); + } + + NodeList v = elem.getElementsByTagName("param-value"); + if (v == null || v.getLength() != 1) { + throw new RuntimeException("Invalid web.xml: 'param-value' tag not found"); + } + params.setInitParameter(n.item(0).getTextContent().trim(), + v.item(0).getTextContent().trim()); + } + + private void loadInitializers(ScanResult scan_res) + { + trace("load initializer(s)"); + + ServiceLoader<ServletContainerInitializer> initializers = + ServiceLoader.load(ServletContainerInitializer.class, loader_); + + for (ServletContainerInitializer sci : initializers) { + + trace("loadInitializers: initializer: " + sci.getClass().getName()); + + HandlesTypes ann = sci.getClass().getAnnotation(HandlesTypes.class); + if (ann == null) { + trace("loadInitializers: no HandlesTypes annotation"); + continue; + } + + Class<?>[] classes = ann.value(); + if (classes == null) { + trace("loadInitializers: no handles classes"); + continue; + } + + Set<Class<?>> handles_classes = new HashSet<>(); + + for (Class<?> c : classes) { + trace("loadInitializers: find handles: " + c.getName()); + + ClassInfoList handles = c.isInterface() + ? scan_res.getClassesImplementing(c.getName()) + : scan_res.getSubclasses(c.getName()); + + for (ClassInfo ci : handles) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + continue; + } + + trace("loadInitializers: handles class: " + ci.getName()); + handles_classes.add(ci.loadClass()); + } + } + + if (handles_classes.isEmpty()) { + trace("loadInitializers: no handles implementations"); + continue; + } + + try { + sci.onStartup(handles_classes, this); + metadata_complete_ = true; + } catch(Exception e) { + System.err.println("loadInitializers: exception caught: " + e.toString()); + } + } + } + + private void scanClasses(ScanResult scan_res) + throws ReflectiveOperationException + { + ClassInfoList filters = scan_res.getClassesWithAnnotation(WebFilter.class.getName()); + + for (ClassInfo ci : filters) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.implementsInterface(Filter.class.getName())) + { + trace("scanClasses: ignoring Filter impl: " + ci.getName()); + continue; + } + + trace("scanClasses: found Filter class: " + ci.getName()); + + Class<?> cls = ci.loadClass(); + if (!Filter.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to Filter"); + continue; + } + + WebFilter ann = cls.getAnnotation(WebFilter.class); + + if (ann == null) { + trace("scanClasses: no WebFilter annotation for " + ci.getName()); + continue; + } + + String filter_name = ann.filterName(); + + if (filter_name.isEmpty()) { + filter_name = ci.getName(); + } + + FilterReg reg = name2filter_.get(filter_name); + + if (reg == null) { + reg = new FilterReg(filter_name, cls); + filters_.add(reg); + name2filter_.put(filter_name, reg); + } else { + reg.setClass(cls); + } + + EnumSet<DispatcherType> dtypes = EnumSet.noneOf(DispatcherType.class); + DispatcherType[] dispatchers = ann.dispatcherTypes(); + for (DispatcherType d : dispatchers) { + dtypes.add(d); + } + + if (dtypes.isEmpty()) { + dtypes.add(DispatcherType.REQUEST); + } + + boolean match_after = false; + + reg.addMappingForUrlPatterns(dtypes, match_after, ann.value()); + reg.addMappingForUrlPatterns(dtypes, match_after, ann.urlPatterns()); + reg.addMappingForServletNames(dtypes, match_after, ann.servletNames()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + ClassInfoList servlets = scan_res.getClassesWithAnnotation(WebServlet.class.getName()); + + for (ClassInfo ci : servlets) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract() + || !ci.extendsSuperclass(HttpServlet.class.getName())) + { + trace("scanClasses: ignoring HttpServlet subclass: " + ci.getName()); + continue; + } + + trace("scanClasses: found HttpServlet class: " + ci.getName()); + + Class<?> cls = ci.loadClass(); + if (!HttpServlet.class.isAssignableFrom(cls)) { + trace("scanClasses: " + ci.getName() + " cannot be assigned to HttpFilter"); + continue; + } + + WebServlet ann = cls.getAnnotation(WebServlet.class); + + if (ann == null) { + trace("scanClasses: no WebServlet annotation"); + continue; + } + + String servlet_name = ann.name(); + + if (servlet_name.isEmpty()) { + servlet_name = ci.getName(); + } + + ServletReg reg = name2servlet_.get(servlet_name); + + if (reg == null) { + reg = new ServletReg(servlet_name, cls); + servlets_.add(reg); + name2servlet_.put(servlet_name, reg); + } else { + reg.setClass(cls); + } + + reg.addMapping(ann.value()); + reg.addMapping(ann.urlPatterns()); + + for (WebInitParam p : ann.initParams()) { + reg.setInitParameter(p.name(), p.value()); + } + + reg.setAsyncSupported(ann.asyncSupported()); + } + + + ClassInfoList lstnrs = scan_res.getClassesWithAnnotation(WebListener.class.getName()); + + for (ClassInfo ci : lstnrs) { + if (ci.isInterface() + || ci.isAnnotation() + || ci.isAbstract()) + { + trace("scanClasses: listener impl: " + ci.getName()); + continue; + } + + trace("scanClasses: listener class: " + ci.getName()); + + if (listener_classnames_.contains(ci.getName())) { + trace("scanClasses: " + ci.getName() + " already added as listener"); + continue; + } + + Class<?> cls = ci.loadClass(); + Class<?> lclass = null; + for (Class<?> c : LISTENER_TYPES) { + if (c.isAssignableFrom(cls)) { + lclass = c; + break; + } + } + + if (lclass == null) { + log("scanClasses: " + ci.getName() + " implements none of known listener interfaces"); + continue; + } + + WebListener ann = cls.getAnnotation(WebListener.class); + + if (ann == null) { + log("scanClasses: no WebListener annotation"); + continue; + } + + Constructor<?> ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(ci.getName()); + } + } + + public void stop() throws IOException + { + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + for (ServletReg s : servlets_) { + s.destroy(); + } + + for (FilterReg f : filters_) { + f.destroy(); + } + + if (!destroy_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : destroy_listeners_) { + listener.contextDestroyed(event); + } + } + + if (extracted_dir_ != null) { + removeDir(extracted_dir_); + } + + if (temp_dir_ != null) { + removeDir(temp_dir_); + } + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + private void removeDir(File dir) throws IOException + { + Files.walkFileTree(dir.toPath(), + new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult postVisitDirectory( + Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile( + Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } + + private class CtxInitParams implements InitParams + { + private final Map<String, String> init_params_ = + new HashMap<String, String>(); + + public boolean setInitParameter(String name, String value) + { + trace("CtxInitParams.setInitParameter " + name + " = " + value); + + return init_params_.putIfAbsent(name, value) == null; + } + + public String getInitParameter(String name) + { + trace("CtxInitParams.getInitParameter for " + name); + + return init_params_.get(name); + } + + public Set<String> setInitParameters(Map<String, String> initParameters) + { + // illegalStateIfContextStarted(); + Set<String> clash = null; + for (Map.Entry<String, String> entry : initParameters.entrySet()) + { + if (entry.getKey() == null) { + throw new IllegalArgumentException("init parameter name required"); + } + + if (entry.getValue() == null) { + throw new IllegalArgumentException("non-null value required for init parameter " + entry.getKey()); + } + + if (init_params_.get(entry.getKey()) != null) + { + if (clash == null) + clash = new HashSet<String>(); + clash.add(entry.getKey()); + } + + trace("CtxInitParams.setInitParameters " + entry.getKey() + " = " + entry.getValue()); + } + + if (clash != null) { + return clash; + } + + init_params_.putAll(initParameters); + return Collections.emptySet(); + } + + public Map<String, String> getInitParameters() + { + trace("CtxInitParams.getInitParameters"); + return init_params_; + } + + public Enumeration<String> getInitParameterNames() + { + return Collections.enumeration(init_params_.keySet()); + } + } + + private class NamedReg extends CtxInitParams + implements Registration + { + private final String name_; + private String class_name_; + + public NamedReg(String name) + { + name_ = name; + } + + public NamedReg(String name, String class_name) + { + name_ = name; + class_name_ = class_name; + } + + @Override + public String getName() + { + return name_; + } + + @Override + public String getClassName() + { + return class_name_; + } + + public void setClassName(String class_name) + { + class_name_ = class_name; + } + } + + private class ServletReg extends NamedReg + implements ServletRegistration.Dynamic, ServletConfig + { + private Class<?> servlet_class_; + private Servlet servlet_; + private String role_; + private boolean async_supported_ = false; + private final List<String> patterns_ = new ArrayList<>(); + private int load_on_startup_ = -1; + private boolean initialized_ = false; + private final List<FilterMap> filters_ = new ArrayList<>(); + private boolean system_jsp_servlet_ = false; + + public ServletReg(String name, Class<?> servlet_class) + { + super(name, servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public ServletReg(String name, Servlet servlet) + { + super(name, servlet.getClass().getName()); + servlet_ = servlet; + } + + public ServletReg(String name, String servlet_class_name) + { + super(name, servlet_class_name); + } + + public ServletReg(String name) + { + super(name); + } + + private void init() throws ServletException + { + if (initialized_) { + return; + } + + trace("ServletReg.init(): " + getName()); + + if (system_jsp_servlet_) { + JasperInitializer ji = new JasperInitializer(); + + ji.onStartup(Collections.emptySet(), Context.this); + } + + if (servlet_ == null) { + try { + if (servlet_class_ == null) { + servlet_class_ = loader_.loadClass(getClassName()); + } + + Constructor<?> ctor = servlet_class_.getConstructor(); + servlet_ = (Servlet) ctor.newInstance(); + } catch(Exception e) { + log("ServletReg.init() failed " + e); + throw new ServletException(e); + } + } + + servlet_.init((ServletConfig) this); + + initialized_ = true; + } + + public void startup() throws ServletException + { + if (load_on_startup_ < 0) { + return; + } + + init(); + } + + public void destroy() + { + if (initialized_) { + servlet_.destroy(); + } + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class<?> servlet_class) + throws IllegalStateException + { + if (servlet_ != null + || servlet_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(servlet_class.getName()); + servlet_class_ = servlet_class; + } + + public void service(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + init(); + + servlet_.service(request, response); + } + + public void addFilter(FilterMap fmap) + { + filters_.add(fmap); + } + + @Override + public Set<String> addMapping(String... urlPatterns) + { + checkContextState(); + + Set<String> clash = null; + for (String pattern : urlPatterns) { + trace("ServletReg.addMapping: " + pattern); + + if (pattern2servlet_.containsKey(pattern)) { + if (clash == null) { + clash = new HashSet<String>(); + } + clash.add(pattern); + } + } + + /* if there were any clashes amongst the urls, return them */ + if (clash != null) { + return clash; + } + + for (String pattern : urlPatterns) { + patterns_.add(pattern); + pattern2servlet_.put(pattern, this); + parseURLPattern(pattern, this); + } + + return Collections.emptySet(); + } + + @Override + public Collection<String> getMappings() + { + trace("ServletReg.getMappings"); + return patterns_; + } + + @Override + public String getRunAsRole() + { + return role_; + } + + @Override + public void setLoadOnStartup(int loadOnStartup) + { + checkContextState(); + + trace("ServletReg.setLoadOnStartup: " + loadOnStartup); + load_on_startup_ = loadOnStartup; + } + + @Override + public Set<String> setServletSecurity(ServletSecurityElement constraint) + { + log("ServletReg.setServletSecurity"); + return Collections.emptySet(); + } + + @Override + public void setMultipartConfig( + MultipartConfigElement multipartConfig) + { + log("ServletReg.setMultipartConfig"); + } + + @Override + public void setRunAsRole(String roleName) + { + log("ServletReg.setRunAsRole: " + roleName); + role_ = roleName; + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("ServletReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getServletName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + public void checkContextState() throws IllegalStateException + { + if (ctx_initialized_) { + throw new IllegalStateException("Context already initialized"); + } + } + + public void parseURLPattern(String p, ServletReg servlet) + throws IllegalArgumentException + { + URLPattern pattern = parseURLPattern(p); + + switch (pattern.type_) { + case PREFIX: + prefix_patterns_.add(new PrefixPattern(pattern.pattern_, servlet)); + return; + + case SUFFIX: + suffix2servlet_.put(pattern.pattern_, servlet); + return; + + case EXACT: + exact2servlet_.put(pattern.pattern_, servlet); + return; + + case DEFAULT: + default_servlet_ = servlet; + return; + } + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public URLPattern parseURLPattern(String p) + throws IllegalArgumentException + { + URLPattern pattern = parsed_patterns_.get(p); + if (pattern == null) { + pattern = new URLPattern(p); + parsed_patterns_.put(p, pattern); + } + + return pattern; + } + + private static enum URLPatternType { + PREFIX, + SUFFIX, + DEFAULT, + EXACT, + }; + + private class URLPattern + { + private final String pattern_; + private final URLPatternType type_; + + public URLPattern(String p) + throws IllegalArgumentException + { + /* + 12.2 Specification of Mappings + ... + A string beginning with a '/' character and ending with a '/*' + suffix is used for path mapping. + */ + if (p.startsWith("/") && p.endsWith("/*")) { + trace("URLPattern: '" + p + "' is a prefix pattern"); + pattern_ = p.substring(0, p.length() - 2); + type_ = URLPatternType.PREFIX; + return; + } + + /* + A string beginning with a '*.' prefix is used as an extension + mapping. + */ + if (p.startsWith("*.")) { + trace("URLPattern: '" + p + "' is a suffix pattern"); + pattern_ = p.substring(1, p.length()); + type_ = URLPatternType.SUFFIX; + return; + } + + /* + The empty string ("") is a special URL pattern that exactly maps to + the application's context root, i.e., requests of the form + http://host:port/<context- root>/. In this case the path info is '/' + and the servlet path and context path is empty string (""). + */ + if (p.isEmpty()) { + trace("URLPattern: '" + p + "' is a root"); + pattern_ = "/"; + type_ = URLPatternType.EXACT; + return; + } + + /* + A string containing only the '/' character indicates the "default" + servlet of the application. In this case the servlet path is the + request URI minus the context path and the path info is null. + */ + if (p.equals("/")) { + trace("URLPattern: '" + p + "' is a default"); + pattern_ = p; + type_ = URLPatternType.DEFAULT; + return; + } + + /* + All other strings are used for exact matches only. + */ + trace("URLPattern: '" + p + "' is an exact pattern"); + pattern_ = p; + type_ = URLPatternType.EXACT; + + /* TODO process other cases, throw IllegalArgumentException */ + } + + public boolean match(String url) + { + switch (type_) { + case PREFIX: + return url.startsWith(pattern_) && ( + url.length() == pattern_.length() + || url.charAt(pattern_.length()) == '/'); + + case SUFFIX: + return url.endsWith(pattern_); + + case EXACT: + return url.equals(pattern_); + + case DEFAULT: + return true; + } + + return false; + } + } + + private class FilterReg extends NamedReg + implements FilterRegistration.Dynamic, FilterConfig + { + private Class<?> filter_class_; + private Filter filter_; + private boolean async_supported_ = false; + private boolean initialized_ = false; + + public FilterReg(String name, Class<?> filter_class) + { + super(name, filter_class.getName()); + filter_class_ = filter_class; + } + + public FilterReg(String name, Filter filter) + { + super(name, filter.getClass().getName()); + filter_ = filter; + } + + public FilterReg(String name, String filter_class_name) + { + super(name, filter_class_name); + } + + public FilterReg(String name) + { + super(name); + } + + public void setClassName(String class_name) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(class_name); + } + + public void setClass(Class<?> filter_class) throws IllegalStateException + { + if (filter_ != null + || filter_class_ != null + || getClassName() != null) + { + throw new IllegalStateException("Class already initialized"); + } + + super.setClassName(filter_class.getName()); + filter_class_ = filter_class; + } + + public void init() throws ServletException + { + if (filter_ == null) { + try { + if (filter_class_ == null) { + filter_class_ = loader_.loadClass(getClassName()); + } + + Constructor<?> ctor = filter_class_.getConstructor(); + filter_ = (Filter) ctor.newInstance(); + } catch(Exception e) { + log("FilterReg.init() failed " + e); + throw new ServletException(e); + } + } + + filter_.init((FilterConfig) this); + + initialized_ = true; + } + + public void destroy() + { + if (initialized_) { + filter_.destroy(); + } + } + + @Override + public void addMappingForServletNames( + EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, + String... servletNames) + { + checkContextState(); + + for (String n : servletNames) { + trace("FilterReg.addMappingForServletNames: ... " + n); + + ServletReg sreg = name2servlet_.get(n); + if (sreg == null) { + sreg = new ServletReg(n); + servlets_.add(sreg); + name2servlet_.put(n, sreg); + } + + FilterMap map = new FilterMap(this, sreg, dispatcherTypes, + isMatchAfter); + + sreg.addFilter(map); + } + } + + @Override + public Collection<String> getServletNameMappings() + { + checkContextState(); + + log("FilterReg.getServletNameMappings"); + return Collections.emptySet(); + } + + @Override + public void addMappingForUrlPatterns( + EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter, + String... urlPatterns) + { + checkContextState(); + + for (String u : urlPatterns) { + trace("FilterReg.addMappingForUrlPatterns: ... " + u); + + URLPattern p = parseURLPattern(u); + FilterMap map = new FilterMap(this, p, dispatcherTypes, + isMatchAfter); + + filter_maps_.add(map); + } + } + + @Override + public Collection<String> getUrlPatternMappings() + { + log("FilterReg.getUrlPatternMappings"); + return Collections.emptySet(); + } + + @Override + public void setAsyncSupported(boolean isAsyncSupported) + { + log("FilterReg.setAsyncSupported: " + isAsyncSupported); + async_supported_ = isAsyncSupported; + } + + @Override + public String getFilterName() + { + return getName(); + } + + @Override + public ServletContext getServletContext() + { + return (ServletContext) Context.this; + } + } + + private class FilterMap + { + private final FilterReg filter_; + private final ServletReg servlet_; + private final URLPattern pattern_; + private final EnumSet<DispatcherType> dtypes_; + private final boolean match_after_; + + public FilterMap(FilterReg filter, ServletReg servlet, + EnumSet<DispatcherType> dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = servlet; + pattern_ = null; + dtypes_ = dtypes; + match_after_ = match_after; + } + + public FilterMap(FilterReg filter, URLPattern pattern, + EnumSet<DispatcherType> dtypes, boolean match_after) + { + filter_ = filter; + servlet_ = null; + pattern_ = pattern; + dtypes_ = dtypes; + match_after_ = match_after; + } + } + + private void initialized() + { + if (!sess_attr_listeners_.isEmpty()) { + sess_attr_proxy_ = new SessionAttrProxy(sess_attr_listeners_); + } + + if (!req_attr_listeners_.isEmpty()) { + req_attr_proxy_ = new RequestAttrProxy(req_attr_listeners_); + } + + ClassLoader old = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(loader_); + + try { + // Call context listeners + destroy_listeners_.clear(); + if (!ctx_listeners_.isEmpty()) { + ServletContextEvent event = new ServletContextEvent(this); + for (ServletContextListener listener : ctx_listeners_) + { + try { + listener.contextInitialized(event); + } catch(AbstractMethodError e) { + log("initialized: AbstractMethodError exception caught: " + e); + } + destroy_listeners_.add(0, listener); + } + } + + for (ServletReg sr : servlets_) { + try { + sr.startup(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + for (FilterReg fr : filters_) { + try { + fr.init(); + } catch(ServletException e) { + log("initialized: exception caught: " + e); + } + } + + ctx_initialized_ = true; + } finally { + Thread.currentThread().setContextClassLoader(old); + } + } + + @Override + public ServletContext getContext(String uripath) + { + trace("getContext for " + uripath); + return this; + } + + @Override + public int getMajorVersion() + { + trace("getMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public String getMimeType(String file) + { + log("getMimeType for " + file); + if (mime_types_ == null) { + mime_types_ = new MimeTypes(); + } + return mime_types_.getMimeByExtension(file); + } + + @Override + public int getMinorVersion() + { + trace("getMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private class URIRequestDispatcher implements RequestDispatcher + { + private final URI uri_; + + public URIRequestDispatcher(URI uri) + { + uri_ = uri; + } + + public URIRequestDispatcher(String uri) + throws URISyntaxException + { + uri_ = new URI(uri); + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + ForwardRequestWrapper req = new ForwardRequestWrapper(request); + + try { + trace("URIRequestDispatcher.forward"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.FORWARD); + + fc.doFilter(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + IncludeRequestWrapper req = new IncludeRequestWrapper(request); + + try { + trace("URIRequestDispatcher.include"); + + String path = uri_.getPath().substring(context_path_.length()); + + ServletReg servlet = findServlet(path, req); + + req.setRequestURI(uri_.getRawPath()); + req.setQueryString(uri_.getRawQuery()); + req.setDispatcherType(DispatcherType.INCLUDE); + + FilterChain fc = new CtxFilterChain(servlet, req.getFilterPath(), DispatcherType.INCLUDE); + + fc.doFilter(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.close(); + + trace("URIRequestDispatcher.include done"); + } + } + } + + private class ServletDispatcher implements RequestDispatcher + { + private final ServletReg servlet_; + + public ServletDispatcher(ServletReg servlet) + { + servlet_ = servlet; + } + + @Override + public void forward(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + /* + 9.4 The Forward Method + ... + If the response has been committed, an IllegalStateException + must be thrown. + */ + if (response.isCommitted()) { + throw new IllegalStateException("Response already committed"); + } + + trace("ServletDispatcher.forward"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.FORWARD); + + /* + 9.4 The Forward Method + ... + If output data exists in the response buffer that has not + been committed, the content must be cleared before the + target servlet's service method is called. + */ + response.resetBuffer(); + + servlet_.service(request, response); + + /* + 9.4 The Forward Method + ... + Before the forward method of the RequestDispatcher interface + returns without exception, the response content must be sent + and committed, and closed by the servlet container, unless + the request was put into the asynchronous mode. If an error + occurs in the target of the RequestDispatcher.forward() the + exception may be propagated back through all the calling + filters and servlets and eventually back to the container + */ + if (!request.isAsyncStarted()) { + response.flushBuffer(); + } + + /* + 9.5 Error Handling + + If the servlet that is the target of a request dispatcher + throws a runtime exception or a checked exception of type + ServletException or IOException, it should be propagated + to the calling servlet. All other exceptions should be + wrapped as ServletExceptions and the root cause of the + exception set to the original exception, as it should + not be propagated. + */ + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.forward done"); + } + } + + @Override + public void include(ServletRequest request, ServletResponse response) + throws ServletException, IOException + { + trace("ServletDispatcher.include"); + + DispatcherType dtype = request.getDispatcherType(); + + Request req; + if (request instanceof Request) { + req = (Request) request; + } else { + req = (Request) request.getAttribute(Request.BARE); + } + + try { + req.setDispatcherType(DispatcherType.INCLUDE); + + servlet_.service(request, new IncludeResponseWrapper(response)); + + } catch (ServletException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new ServletException(e); + } finally { + req.setDispatcherType(dtype); + + trace("ServletDispatcher.include done"); + } + } + } + + @Override + public RequestDispatcher getNamedDispatcher(String name) + { + trace("getNamedDispatcher for " + name); + + ServletReg servlet = name2servlet_.get(name); + if (servlet != null) { + return new ServletDispatcher(servlet); + } + + return null; + } + + @Override + public RequestDispatcher getRequestDispatcher(String uriInContext) + { + trace("getRequestDispatcher for " + uriInContext); + try { + return new URIRequestDispatcher(context_path_ + uriInContext); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + public RequestDispatcher getRequestDispatcher(URI uri) + { + trace("getRequestDispatcher for " + uri.getRawPath()); + return new URIRequestDispatcher(uri); + } + + @Override + public String getRealPath(String path) + { + trace("getRealPath for " + path); + + File f = new File(webapp_, path.substring(1)); + + return f.getAbsolutePath(); + } + + @Override + public URL getResource(String path) throws MalformedURLException + { + trace("getResource for " + path); + + File f = new File(webapp_, path.substring(1)); + + if (f.exists()) { + return new URL("file:" + f.getAbsolutePath()); + } + + return null; + } + + @Override + public InputStream getResourceAsStream(String path) + { + trace("getResourceAsStream for " + path); + + try { + File f = new File(webapp_, path.substring(1)); + + return new FileInputStream(f); + } catch (FileNotFoundException e) { + log("getResourceAsStream: failed " + e); + + return null; + } + } + + @Override + public Set<String> getResourcePaths(String path) + { + trace("getResourcePaths for " + path); + + File dir = new File(webapp_, path.substring(1)); + File[] list = dir.listFiles(); + + if (list == null) { + return null; + } + + Set<String> res = new HashSet<>(); + Path root = webapp_.toPath(); + + for (File f : list) { + String r = "/" + root.relativize(f.toPath()); + if (f.isDirectory()) { + r += "/"; + } + + trace("getResourcePaths: " + r); + + res.add(r); + } + + return res; + } + + @Override + public String getServerInfo() + { + trace("getServerInfo: " + server_info_); + return server_info_; + } + + @Override + @Deprecated + public Servlet getServlet(String name) throws ServletException + { + log("getServlet for " + name); + return null; + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration<String> getServletNames() + { + log("getServletNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @SuppressWarnings("unchecked") + @Override + @Deprecated + public Enumeration<Servlet> getServlets() + { + log("getServlets"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + @Deprecated + public void log(Exception exception, String msg) + { + log(msg, exception); + } + + @Override + public void log(String msg) + { + msg = "Context." + msg; + log(0, msg, msg.length()); + } + + @Override + public void log(String message, Throwable throwable) + { + log(message); + } + + private static native void log(long ctx_ptr, String msg, int msg_len); + + + public static void trace(String msg) + { + msg = "Context." + msg; + trace(0, msg, msg.length()); + } + + private static native void trace(long ctx_ptr, String msg, int msg_len); + + @Override + public String getInitParameter(String name) + { + trace("getInitParameter for " + name); + return init_params_.get(name); + } + + @SuppressWarnings("unchecked") + @Override + public Enumeration<String> getInitParameterNames() + { + trace("getInitParameterNames"); + return Collections.enumeration(Collections.EMPTY_LIST); + } + + @Override + public String getServletContextName() + { + log("getServletContextName"); + return "No Context"; + } + + @Override + public String getContextPath() + { + trace("getContextPath"); + return context_path_; + } + + @Override + public boolean setInitParameter(String name, String value) + { + trace("setInitParameter " + name + " = " + value); + return init_params_.putIfAbsent(name, value) == null; + } + + @Override + public Object getAttribute(String name) + { + trace("getAttribute " + name); + + return attributes_.get(name); + } + + @Override + public Enumeration<String> getAttributeNames() + { + trace("getAttributeNames"); + + Set<String> names = attributes_.keySet(); + return Collections.enumeration(names); + } + + @Override + public void setAttribute(String name, Object object) + { + trace("setAttribute " + name); + + Object prev = attributes_.put(name, object); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, prev == null ? object : prev); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + if (prev == null) { + l.attributeAdded(scae); + } else { + l.attributeReplaced(scae); + } + } + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute " + name); + + Object value = attributes_.remove(name); + + if (ctx_attr_listeners_.isEmpty()) { + return; + } + + ServletContextAttributeEvent scae = new ServletContextAttributeEvent( + this, name, value); + + for (ServletContextAttributeListener l : ctx_attr_listeners_) { + l.attributeRemoved(scae); + } + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, + Class<? extends Filter> filterClass) + { + log("addFilter<C> " + name + ", " + filterClass.getName()); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filterClass); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, Filter filter) + { + log("addFilter<F> " + name); + + checkContextState(); + + FilterReg reg = new FilterReg(name, filter); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public FilterRegistration.Dynamic addFilter(String name, String className) + { + log("addFilter<N> " + name + ", " + className); + + checkContextState(); + + FilterReg reg = new FilterReg(name, className); + filters_.add(reg); + name2filter_.put(name, reg); + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, + Class<? extends Servlet> servletClass) + { + log("addServlet<C> " + name + ", " + servletClass.getName()); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servletClass); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, Servlet servlet) + { + log("addServlet<S> " + name); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, servlet); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addServlet(String name, String className) + { + log("addServlet<N> " + name + ", " + className); + + checkContextState(); + + ServletReg reg = null; + try { + reg = new ServletReg(name, className); + servlets_.add(reg); + name2servlet_.put(name, reg); + } catch(Exception e) { + System.err.println("addServlet: exception caught: " + e.toString()); + } + + return reg; + } + + @Override + public ServletRegistration.Dynamic addJspFile(String jspName, String jspFile) + { + log("addJspFile: " + jspName + " " + jspFile); + + return null; + } + + @Override + public <T extends Filter> T createFilter(Class<T> c) throws ServletException + { + log("createFilter<C> " + c.getName()); + + checkContextState(); + + try { + Constructor<T> ctor = c.getConstructor(); + T filter = ctor.newInstance(); + return filter; + } catch (Exception e) { + log("createFilter() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public <T extends Servlet> T createServlet(Class<T> c) throws ServletException + { + log("createServlet<C> " + c.getName()); + + checkContextState(); + + try { + Constructor<T> ctor = c.getConstructor(); + T servlet = ctor.newInstance(); + return servlet; + } catch (Exception e) { + log("createServlet() failed " + e); + + throw new ServletException(e); + } + } + + @Override + public Set<SessionTrackingMode> getDefaultSessionTrackingModes() + { + log("getDefaultSessionTrackingModes"); + + return default_session_tracking_modes_; + } + + @Override + public Set<SessionTrackingMode> getEffectiveSessionTrackingModes() + { + log("getEffectiveSessionTrackingModes"); + + return session_tracking_modes_; + } + + public boolean isSessionIdValid(String id) + { + synchronized (sessions_) { + return sessions_.containsKey(id); + } + } + + public Session getSession(String id) + { + synchronized (sessions_) { + Session s = sessions_.get(id); + + if (s != null) { + s.accessed(); + + if (s.checkTimeOut()) { + s.invalidate(); + return null; + } + } + + return s; + } + } + + public Session createSession() + { + Session session = new Session(this, generateSessionId(), + sess_attr_proxy_, session_timeout_ * 60); + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (HttpSessionListener l : sess_listeners_) + { + l.sessionCreated(event); + } + } + + synchronized (sessions_) { + sessions_.put(session.getId(), session); + + return session; + } + } + + public void invalidateSession(Session session) + { + synchronized (sessions_) { + sessions_.remove(session.getId()); + } + + if (!sess_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + + for (int i = sess_listeners_.size() - 1; i >= 0; i--) + { + sess_listeners_.get(i).sessionDestroyed(event); + } + } + } + + public void changeSessionId(Session session) + { + String old_id; + + synchronized (sessions_) { + old_id = session.getId(); + sessions_.remove(old_id); + + session.setId(generateSessionId()); + + sessions_.put(session.getId(), session); + } + + if (!sess_id_listeners_.isEmpty()) + { + HttpSessionEvent event = new HttpSessionEvent(session); + for (HttpSessionIdListener l : sess_id_listeners_) + { + l.sessionIdChanged(event, old_id); + } + } + } + + private String generateSessionId() + { + return UUID.randomUUID().toString(); + } + + @Override + public FilterRegistration getFilterRegistration(String filterName) + { + log("getFilterRegistration " + filterName); + return name2filter_.get(filterName); + } + + @Override + public Map<String, ? extends FilterRegistration> getFilterRegistrations() + { + log("getFilterRegistrations"); + return name2filter_; + } + + @Override + public ServletRegistration getServletRegistration(String servletName) + { + log("getServletRegistration " + servletName); + return name2servlet_.get(servletName); + } + + @Override + public Map<String, ? extends ServletRegistration> getServletRegistrations() + { + log("getServletRegistrations"); + return name2servlet_; + } + + @Override + public SessionCookieConfig getSessionCookieConfig() + { + log("getSessionCookieConfig"); + + return session_cookie_config_; + } + + @Override + public void setSessionTrackingModes(Set<SessionTrackingMode> modes) + { + log("setSessionTrackingModes"); + + session_tracking_modes_ = modes; + } + + @Override + public void addListener(String className) + { + trace("addListener<N> " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener<N> " + className + " already added as listener"); + return; + } + + try { + Class<?> cls = loader_.loadClass(className); + + Constructor<?> ctor = cls.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener<N>: exception caught: " + e.toString()); + } + } + + @Override + public <T extends EventListener> void addListener(T t) + { + trace("addListener<T> " + t.getClass().getName()); + + checkContextState(); + + for (int i = 0; i < LISTENER_TYPES.length; i++) { + Class<?> c = LISTENER_TYPES[i]; + if (c.isAssignableFrom(t.getClass())) { + trace("addListener<T>: assignable to " + c.getName()); + } + } + + if (t instanceof ServletContextListener) { + ctx_listeners_.add((ServletContextListener) t); + } + + if (t instanceof ServletContextAttributeListener) { + ctx_attr_listeners_.add((ServletContextAttributeListener) t); + } + + if (t instanceof ServletRequestListener) { + req_init_listeners_.add((ServletRequestListener) t); + req_destroy_listeners_.add(0, (ServletRequestListener) t); + } + + if (t instanceof ServletRequestAttributeListener) { + req_attr_listeners_.add((ServletRequestAttributeListener) t); + } + + if (t instanceof HttpSessionAttributeListener) { + sess_attr_listeners_.add((HttpSessionAttributeListener) t); + } + + if (t instanceof HttpSessionIdListener) { + sess_id_listeners_.add((HttpSessionIdListener) t); + } + + if (t instanceof HttpSessionListener) { + sess_listeners_.add((HttpSessionListener) t); + } + } + + @Override + public void addListener(Class<? extends EventListener> listenerClass) + { + String className = listenerClass.getName(); + trace("addListener<C> " + className); + + checkContextState(); + + if (listener_classnames_.contains(className)) { + log("addListener<C> " + className + " already added as listener"); + return; + } + + try { + Constructor<?> ctor = listenerClass.getConstructor(); + EventListener listener = (EventListener) ctor.newInstance(); + + addListener(listener); + + listener_classnames_.add(className); + } catch (Exception e) { + log("addListener<C>: exception caught: " + e.toString()); + } + } + + @Override + public <T extends EventListener> T createListener(Class<T> clazz) + throws ServletException + { + trace("createListener<C> " + clazz.getName()); + + checkContextState(); + + try + { + return clazz.getDeclaredConstructor().newInstance(); + } + catch (Exception e) + { + throw new ServletException(e); + } + } + + @Override + public ClassLoader getClassLoader() + { + trace("getClassLoader"); + return loader_; + } + + @Override + public int getEffectiveMajorVersion() + { + log("getEffectiveMajorVersion"); + return SERVLET_MAJOR_VERSION; + } + + @Override + public int getEffectiveMinorVersion() + { + log("getEffectiveMinorVersion"); + return SERVLET_MINOR_VERSION; + } + + private final List<TaglibDescriptor> taglibs_ = new ArrayList<>(); + private final List<JspPropertyGroupDescriptor> prop_groups_ = new ArrayList<>(); + + private class JspConfig implements JspConfigDescriptor + { + @Override + public Collection<TaglibDescriptor> getTaglibs() + { + trace("getTaglibs"); + return taglibs_; + } + + @Override + public Collection<JspPropertyGroupDescriptor> getJspPropertyGroups() + { + trace("getJspPropertyGroups"); + return prop_groups_; + } + } + + private final JspConfig jsp_config_ = new JspConfig(); + + @Override + public JspConfigDescriptor getJspConfigDescriptor() + { + trace("getJspConfigDescriptor"); + + return jsp_config_; + } + + @Override + public void declareRoles(String... roleNames) + { + log("declareRoles"); + //LOG.warn(__unimplmented); + } + + @Override + public String getVirtualServerName() + { + log("getVirtualServerName"); + return null; + } + + @Override + public int getSessionTimeout() + { + trace("getSessionTimeout"); + + return session_timeout_; + } + + @Override + public void setSessionTimeout(int sessionTimeout) + { + trace("setSessionTimeout: " + sessionTimeout); + + session_timeout_ = sessionTimeout; + } + + @Override + public String getRequestCharacterEncoding() + { + log("getRequestCharacterEncoding"); + + return null; + } + + @Override + public void setRequestCharacterEncoding(String encoding) + { + log("setRequestCharacterEncoding: " + encoding); + } + + @Override + public String getResponseCharacterEncoding() + { + log("getResponseCharacterEncoding"); + + return null; + } + + @Override + public void setResponseCharacterEncoding(String encoding) + { + log("setResponseCharacterEncoding: " + encoding); + } + + public ServletRequestAttributeListener getRequestAttributeListener() + { + return req_attr_proxy_; + } +} diff --git a/src/java/nginx/unit/DynamicDispatcherRequest.java b/src/java/nginx/unit/DynamicDispatcherRequest.java new file mode 100644 index 00000000..af0747eb --- /dev/null +++ b/src/java/nginx/unit/DynamicDispatcherRequest.java @@ -0,0 +1,8 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; + +public interface DynamicDispatcherRequest +{ + public void setDispatcherType(DispatcherType type); +} diff --git a/src/java/nginx/unit/DynamicPathRequest.java b/src/java/nginx/unit/DynamicPathRequest.java new file mode 100644 index 00000000..efc1bcd1 --- /dev/null +++ b/src/java/nginx/unit/DynamicPathRequest.java @@ -0,0 +1,15 @@ +package nginx.unit; + +public interface DynamicPathRequest + extends DynamicDispatcherRequest +{ + public void setServletPath(String servlet_path, String path_info); + + public void setServletPath(String filter_path, String servlet_path, String path_info); + + public void setRequestURI(String uri); + + public void setQueryString(String query); + + public String getFilterPath(); +} diff --git a/src/java/nginx/unit/ForwardRequestWrapper.java b/src/java/nginx/unit/ForwardRequestWrapper.java new file mode 100644 index 00000000..f88b6aef --- /dev/null +++ b/src/java/nginx/unit/ForwardRequestWrapper.java @@ -0,0 +1,150 @@ +package nginx.unit; + +import java.util.List; +import java.util.Map; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; + +public class ForwardRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final boolean keep_attrs; + + private final String orig_filter_path; + private final String orig_servlet_path; + private final String orig_path_info; + private final String orig_uri; + private final String orig_context_path; + private final String orig_query; + + private final DispatcherType orig_dtype; + + private MultiMap<String> orig_parameters; + + public ForwardRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + keep_attrs = request_.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null; + + orig_dtype = request_.getDispatcherType(); + + orig_filter_path = request_.getFilterPath(); + orig_servlet_path = request_.getServletPath(); + orig_path_info = request_.getPathInfo(); + orig_uri = request_.getRequestURI(); + orig_context_path = request_.getContextPath(); + orig_query = request_.getQueryString(); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + + /* + 9.4.2 Forwarded Request Parameters + ... + Note that these attributes must always reflect the information in + the original request even under the situation that multiple + forwards and subsequent includes are called. + */ + + if (keep_attrs) { + return; + } + + /* + 9.4.2 Forwarded Request Parameters + ... + The values of these attributes must be equal to the return values + of the HttpServletRequest methods getRequestURI, getContextPath, + getServletPath, getPathInfo, getQueryString respectively, invoked + on the request object passed to the first servlet object in the + call chain that received the request from the client. + */ + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, orig_servlet_path); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, orig_path_info); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, orig_uri); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, orig_context_path); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, orig_query); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + request_.setServletPath(servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setServletPath(filter_path, servlet_path, path_info); + } + + @Override + public void setRequestURI(String uri) + { + request_.setRequestURI(uri); + } + + @Override + public void setQueryString(String query) + { + if (query != null) { + orig_parameters = request_.getParameters(); + + MultiMap<String> parameters = new MultiMap<>(); + UrlEncoded.decodeUtf8To(query, parameters); + + for (Map.Entry<String, List<String>> e: orig_parameters.entrySet()) { + parameters.addValues(e.getKey(), e.getValue()); + } + + request_.setParameters(parameters); + + request_.setQueryString(query); + } + } + + @Override + public String getFilterPath() + { + return request_.getFilterPath(); + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setRequestURI(orig_uri); + request_.setServletPath(orig_filter_path, orig_servlet_path, orig_path_info); + request_.setQueryString(orig_query); + + if (orig_parameters != null) { + request_.setParameters(orig_parameters); + } + + if (keep_attrs) { + return; + } + + request_.setAttribute_(RequestDispatcher.FORWARD_SERVLET_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_PATH_INFO, null); + request_.setAttribute_(RequestDispatcher.FORWARD_REQUEST_URI, null); + request_.setAttribute_(RequestDispatcher.FORWARD_CONTEXT_PATH, null); + request_.setAttribute_(RequestDispatcher.FORWARD_QUERY_STRING, null); + } +} diff --git a/src/java/nginx/unit/HeaderNamesEnumeration.java b/src/java/nginx/unit/HeaderNamesEnumeration.java new file mode 100644 index 00000000..d81b8778 --- /dev/null +++ b/src/java/nginx/unit/HeaderNamesEnumeration.java @@ -0,0 +1,42 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class HeaderNamesEnumeration implements Enumeration<String> { + + private long headers_ptr; + private long size; + private long pos = 0; + + public HeaderNamesEnumeration(long _headers_ptr, long _size) { + headers_ptr = _headers_ptr; + size = _size; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long pos); + + @Override + public String nextElement() + { + if (pos >= size) { + throw new NoSuchElementException(); + } + + return nextElement(headers_ptr, size, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long pos); +} diff --git a/src/java/nginx/unit/HeadersEnumeration.java b/src/java/nginx/unit/HeadersEnumeration.java new file mode 100644 index 00000000..31b5ae24 --- /dev/null +++ b/src/java/nginx/unit/HeadersEnumeration.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.lang.String; +import java.util.Enumeration; + +public class HeadersEnumeration implements Enumeration<String> { + + private long headers_ptr; + private long size; + private long initial_pos; + private long pos; + + public HeadersEnumeration(long _headers_ptr, long _size, long _initial_pos) { + headers_ptr = _headers_ptr; + size = _size; + initial_pos = _initial_pos; + pos = _initial_pos; + } + + @Override + public boolean hasMoreElements() + { + if (pos >= size) { + return false; + } + + pos = nextElementPos(headers_ptr, size, initial_pos, pos); + return pos < size; + } + + static private native long nextElementPos(long headers_ptr, long size, long initial_pos, long pos); + + @Override + public String nextElement() + { + return nextElement(headers_ptr, size, initial_pos, pos++); + } + + static private native String nextElement(long headers_ptr, long size, long initial_pos, long pos); +} diff --git a/src/java/nginx/unit/IncludeRequestWrapper.java b/src/java/nginx/unit/IncludeRequestWrapper.java new file mode 100644 index 00000000..67a51b24 --- /dev/null +++ b/src/java/nginx/unit/IncludeRequestWrapper.java @@ -0,0 +1,88 @@ +package nginx.unit; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletRequest; + +public class IncludeRequestWrapper implements DynamicPathRequest +{ + private final Request request_; + + private final Object orig_servlet_path_attr; + private final Object orig_path_info_attr; + private final Object orig_uri_attr; + private final Object orig_context_path_attr; + private final Object orig_query_string_attr; + + private final DispatcherType orig_dtype; + + private String filter_path_; + + public IncludeRequestWrapper(ServletRequest request) + { + if (request instanceof Request) { + request_ = (Request) request; + } else { + request_ = (Request) request.getAttribute(Request.BARE); + } + + orig_servlet_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH); + orig_path_info_attr = request_.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO); + orig_uri_attr = request_.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI); + orig_context_path_attr = request_.getAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH); + orig_query_string_attr = request_.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING); + + orig_dtype = request_.getDispatcherType(); + + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, request_.getContextPath()); + } + + @Override + public void setDispatcherType(DispatcherType type) + { + request_.setDispatcherType(type); + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + setServletPath(servlet_path, servlet_path, path_info); + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, servlet_path); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, path_info); + filter_path_ = filter_path; + } + + @Override + public void setRequestURI(String uri) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, uri); + } + + @Override + public void setQueryString(String query) + { + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, query); + } + + @Override + public String getFilterPath() + { + return filter_path_; + } + + public void close() + { + request_.setDispatcherType(orig_dtype); + + request_.setAttribute_(RequestDispatcher.INCLUDE_SERVLET_PATH, orig_servlet_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_PATH_INFO, orig_path_info_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_REQUEST_URI, orig_uri_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_CONTEXT_PATH, orig_context_path_attr); + request_.setAttribute_(RequestDispatcher.INCLUDE_QUERY_STRING, orig_query_string_attr); + } +} diff --git a/src/java/nginx/unit/IncludeResponseWrapper.java b/src/java/nginx/unit/IncludeResponseWrapper.java new file mode 100644 index 00000000..4537114a --- /dev/null +++ b/src/java/nginx/unit/IncludeResponseWrapper.java @@ -0,0 +1,117 @@ +package nginx.unit; + +import java.io.IOException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; + +public class IncludeResponseWrapper extends HttpServletResponseWrapper { + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + public IncludeResponseWrapper(ServletResponse response) + { + super((HttpServletResponse) response); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + } + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + } + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + } + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + } + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + } + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + } + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + } + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + } + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + } + + @Override + public void reset() + { + trace("reset"); + } + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + } + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + } + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + } + + private void trace(String msg) + { + msg = "IncludeResponse." + msg; + Response.trace(0, msg.getBytes(UTF_8)); + } +} diff --git a/src/java/nginx/unit/InitParams.java b/src/java/nginx/unit/InitParams.java new file mode 100644 index 00000000..2f5dcbf9 --- /dev/null +++ b/src/java/nginx/unit/InitParams.java @@ -0,0 +1,7 @@ +package nginx.unit; + +public interface InitParams { + public boolean setInitParameter(String name, String value); + + public String getInitParameter(String name); +} diff --git a/src/java/nginx/unit/InputStream.java b/src/java/nginx/unit/InputStream.java new file mode 100644 index 00000000..6fe72ace --- /dev/null +++ b/src/java/nginx/unit/InputStream.java @@ -0,0 +1,90 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +public class InputStream extends ServletInputStream { + + private long req_info_ptr; + + public InputStream(long ptr) + { + req_info_ptr = ptr; + } + + @Override + public int readLine(byte[] b, int off, int len) throws IOException { + + if (len <= 0) { + return 0; + } + return readLine(req_info_ptr, b, off, len); + } + + private static native int readLine(long req_info_ptr, byte[] b, int off, int len); + + + @Override + public boolean isFinished() + { + return isFinished(req_info_ptr); + } + + private static native boolean isFinished(long req_info_ptr); + + + @Override + public boolean isReady() + { + return true; + } + + + @Override + public void setReadListener(ReadListener listener) + { + } + + + @Override + public int read() throws IOException + { + return read(req_info_ptr); + } + + private static native int read(long req_info_ptr); + + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + return read(req_info_ptr, b, off, len); + } + + private static native int read(long req_info_ptr, byte b[], int off, int len); + + + @Override + public long skip(long n) throws IOException { + return skip(req_info_ptr, n); + } + + private static native long skip(long req_info_ptr, long n); + + + @Override + public int available() throws IOException { + return available(req_info_ptr); + } + + private static native int available(long req_info_ptr); +} diff --git a/src/java/nginx/unit/JspPropertyGroup.java b/src/java/nginx/unit/JspPropertyGroup.java new file mode 100644 index 00000000..2df27718 --- /dev/null +++ b/src/java/nginx/unit/JspPropertyGroup.java @@ -0,0 +1,169 @@ +package nginx.unit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.descriptor.JspPropertyGroupDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class JspPropertyGroup implements JspPropertyGroupDescriptor +{ + private final List<String> url_patterns_ = new ArrayList<>(); + private String el_ignored_ = null; + private String page_encoding_ = null; + private String scripting_invalid_ = null; + private String is_xml_ = null; + private final List<String> include_preludes_ = new ArrayList<>(); + private final List<String> include_codas_ = new ArrayList<>(); + + private String deffered_syntax_allowed_as_literal_ = null; + private String trim_directive_whitespaces_ = null; + private String default_content_type_ = null; + private String buffer_ = null; + private String error_on_undeclared_namespace_ = null; + + public JspPropertyGroup(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("url-pattern")) { + url_patterns_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("el-ignored")) { + el_ignored_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("page-encoding")) { + page_encoding_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("scripting-invalid")) { + scripting_invalid_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("is-xml")) { + is_xml_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("include-prelude")) { + include_preludes_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("include-coda")) { + include_codas_.add(node.getTextContent().trim()); + continue; + } + + if (tag_name.equals("deferred-syntax-allowed-as-literal")) { + deffered_syntax_allowed_as_literal_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("trim-directive-whitespaces")) { + trim_directive_whitespaces_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("default-content-type")) { + default_content_type_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("buffer")) { + buffer_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("error-on-undeclared-namespace")) { + error_on_undeclared_namespace_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public Collection<String> getUrlPatterns() + { + return new ArrayList<>(url_patterns_); + } + + @Override + public String getElIgnored() + { + return el_ignored_; + } + + @Override + public String getPageEncoding() + { + return page_encoding_; + } + + @Override + public String getScriptingInvalid() + { + return scripting_invalid_; + } + + @Override + public String getIsXml() + { + return is_xml_; + } + + @Override + public Collection<String> getIncludePreludes() + { + return new ArrayList<>(include_preludes_); + } + + @Override + public Collection<String> getIncludeCodas() + { + return new ArrayList<>(include_codas_); + } + + @Override + public String getDeferredSyntaxAllowedAsLiteral() + { + return deffered_syntax_allowed_as_literal_; + } + + @Override + public String getTrimDirectiveWhitespaces() + { + return trim_directive_whitespaces_; + } + + @Override + public String getDefaultContentType() + { + return default_content_type_; + } + + @Override + public String getBuffer() + { + return buffer_; + } + + @Override + public String getErrorOnUndeclaredNamespace() + { + return error_on_undeclared_namespace_; + } +} + diff --git a/src/java/nginx/unit/OutputStream.java b/src/java/nginx/unit/OutputStream.java new file mode 100644 index 00000000..68af3423 --- /dev/null +++ b/src/java/nginx/unit/OutputStream.java @@ -0,0 +1,68 @@ +package nginx.unit; + +import java.io.IOException; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; + +public class OutputStream extends ServletOutputStream { + + private long req_info_ptr; + + public OutputStream(long ptr) { + req_info_ptr = ptr; + } + + @Override + public void write(int b) throws IOException + { + write(req_info_ptr, b); + } + + private static native void write(long req_info_ptr, int b); + + + @Override + public void write(byte b[], int off, int len) throws IOException + { + if (b == null) { + throw new NullPointerException(); + } else if ((off < 0) || (off > b.length) || (len < 0) || + ((off + len) > b.length) || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + + write(req_info_ptr, b, off, len); + } + + private static native void write(long req_info_ptr, byte b[], int off, int len); + + @Override + public void flush() + { + flush(req_info_ptr); + } + + private static native void flush(long req_info_ptr); + + @Override + public void close() + { + close(req_info_ptr); + } + + private static native void close(long req_info_ptr); + + @Override + public boolean isReady() + { + return true; + } + + @Override + public void setWriteListener(WriteListener listener) + { + } +} diff --git a/src/java/nginx/unit/Request.java b/src/java/nginx/unit/Request.java new file mode 100644 index 00000000..dc73c656 --- /dev/null +++ b/src/java/nginx/unit/Request.java @@ -0,0 +1,1123 @@ +package nginx.unit; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +import java.lang.IllegalArgumentException; +import java.lang.IllegalStateException; +import java.lang.Object; +import java.lang.String; +import java.lang.StringBuffer; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import java.security.Principal; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; +import javax.servlet.ServletResponse; +import javax.servlet.SessionTrackingMode; +import javax.servlet.SessionCookieConfig; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import org.eclipse.jetty.util.MultiMap; +import org.eclipse.jetty.util.UrlEncoded; +import org.eclipse.jetty.util.StringUtil; + +import org.eclipse.jetty.server.CookieCutter; +import org.eclipse.jetty.http.MimeTypes; + +public class Request implements HttpServletRequest, DynamicPathRequest +{ + private final Context context; + private final long req_info_ptr; + private final long req_ptr; + + protected String authType = null; + + protected boolean cookiesParsed = false; + + protected CookieCutter cookies = null; + + private final Map<String, Object> attributes = new HashMap<>(); + + private MultiMap<String> parameters = null; + + private final String context_path; + private String filter_path = null; + private String servlet_path = null; + private String path_info = null; + private String request_uri = null; + private String query_string = null; + private boolean query_string_valid = false; + + private DispatcherType dispatcher_type = DispatcherType.REQUEST; + + private String characterEncoding = null; + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat formats[] = { + new SimpleDateFormat(RFC1123_DATE, Locale.US), + new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), + new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) + }; + + private InputStream inputStream = null; + private BufferedReader reader = null; + + private boolean request_session_id_parsed = false; + private String request_session_id = null; + private boolean request_session_id_from_cookie = false; + private boolean request_session_id_from_url = false; + private Session session = null; + + private final ServletRequestAttributeListener attr_listener; + + public static final String BARE = "nginx.unit.request.bare"; + + public Request(Context ctx, long req_info, long req) { + context = ctx; + req_info_ptr = req_info; + req_ptr = req; + + attr_listener = context.getRequestAttributeListener(); + context_path = context.getContextPath(); + } + + @Override + public boolean authenticate(HttpServletResponse response) + throws IOException, ServletException + { + log("authenticate"); + + if (response.isCommitted()) { + throw new IllegalStateException(); + } + + return false; + } + + @Override + public String getAuthType() + { + log("getAuthType"); + + return authType; + } + + @Override + public String getContextPath() + { + trace("getContextPath: " + context_path); + + return context_path; + } + + @Override + public Cookie[] getCookies() + { + trace("getCookies"); + + if (!cookiesParsed) { + parseCookies(); + } + + //Javadoc for Request.getCookies() stipulates null for no cookies + if (cookies == null || cookies.getCookies().length == 0) { + return null; + } + + return cookies.getCookies(); + } + + protected void parseCookies() + { + cookiesParsed = true; + + cookies = new CookieCutter(); + + Enumeration<String> cookie_headers = getHeaders("Cookie"); + + while (cookie_headers.hasMoreElements()) { + cookies.addCookieField(cookie_headers.nextElement()); + } + } + + @Override + public long getDateHeader(String name) + { + trace("getDateHeader: " + name); + + String value = getHeader(name); + if (value == null) { + return -1L; + } + + long res = parseDate(value); + if (res == -1L) { + throw new IllegalArgumentException(value); + } + + return res; + } + + protected long parseDate(String value) + { + Date date = null; + for (int i = 0; (date == null) && (i < formats.length); i++) { + try { + date = formats[i].parse(value); + } catch (ParseException e) { + // Ignore + } + } + if (date == null) { + return -1L; + } + return date.getTime(); + } + + @Override + public String getHeader(String name) + { + String res = getHeader(req_ptr, name, name.length()); + + trace("getHeader: " + name + " = '" + res + "'"); + + return res; + } + + private static native String getHeader(long req_ptr, String name, int name_len); + + + @Override + public Enumeration<String> getHeaderNames() + { + trace("getHeaderNames"); + + return getHeaderNames(req_ptr); + } + + private static native Enumeration<String> getHeaderNames(long req_ptr); + + + @Override + public Enumeration<String> getHeaders(String name) + { + trace("getHeaders: " + name); + + return getHeaders(req_ptr, name, name.length()); + } + + private static native Enumeration<String> getHeaders(long req_ptr, String name, int name_len); + + + @Override + public int getIntHeader(String name) + { + trace("getIntHeader: " + name); + + return getIntHeader(req_ptr, name, name.length()); + } + + private static native int getIntHeader(long req_ptr, String name, int name_len); + + + @Override + public String getMethod() + { + trace("getMethod"); + + return getMethod(req_ptr); + } + + private static native String getMethod(long req_ptr); + + + @Override + public Part getPart(String name) throws IOException, ServletException + { + log("getPart: " + name); + + return null; + } + + @Override + public Collection<Part> getParts() throws IOException, ServletException + { + log("getParts"); + + return Collections.emptyList(); + } + + @Override + public String getPathInfo() + { + trace("getPathInfo: " + path_info); + + return path_info; + } + + @Override + public String getPathTranslated() + { + trace("getPathTranslated"); + + if (path_info == null) { + return null; + } + + return context.getRealPath(path_info); + } + + @Override + public String getQueryString() + { + if (!query_string_valid) { + query_string = getQueryString(req_ptr); + query_string_valid = true; + } + + trace("getQueryString: " + query_string); + + return query_string; + } + + private static native String getQueryString(long req_ptr); + + @Override + public void setQueryString(String query) + { + trace("setQueryString: " + query); + + query_string = query; + query_string_valid = true; + } + + @Override + public String getRemoteUser() + { + log("getRemoteUser"); + + /* TODO */ + return null; + } + + @Override + public String getRequestedSessionId() + { + trace("getRequestedSessionId"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id; + } + + private void parseRequestSessionId() + { + request_session_id_parsed = true; + + Cookie[] cookies = getCookies(); + if (cookies == null) { + return; + } + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + final String name = context.getSessionCookieConfig().getName(); + + for (Cookie c : cookies) { + if (c.getName().equals(name)) { + request_session_id = c.getValue(); + request_session_id_from_cookie = true; + + return; + } + } + } + } + + @Override + public String getRequestURI() + { + if (request_uri == null) { + request_uri = getRequestURI(req_ptr); + } + + trace("getRequestURI: " + request_uri); + + return request_uri; + } + + private static native String getRequestURI(long req_ptr); + + + @Override + public void setRequestURI(String uri) + { + trace("setRequestURI: " + uri); + + request_uri = uri; + } + + + @Override + public StringBuffer getRequestURL() + { + String host = getHeader("Host"); + String uri = getRequestURI(); + StringBuffer res = new StringBuffer("http://" + host + uri); + + trace("getRequestURL: " + res); + + return res; + } + + @Override + public String getServletPath() + { + trace("getServletPath: " + servlet_path); + + return servlet_path; + } + + @Override + public void setServletPath(String servlet_path, String path_info) + { + trace("setServletPath: " + servlet_path); + + this.filter_path = servlet_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public void setServletPath(String filter_path, String servlet_path, String path_info) + { + trace("setServletPath: " + filter_path + ", " + servlet_path); + + this.filter_path = filter_path; + this.servlet_path = servlet_path; + this.path_info = path_info; + } + + @Override + public String getFilterPath() + { + return filter_path; + } + + @Override + public HttpSession getSession() + { + return getSession(true); + } + + @Override + public HttpSession getSession(boolean create) + { + if (session != null) { + if (context.isSessionIdValid(session.getId())) { + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + session = null; + } + + if (!request_session_id_parsed) { + parseRequestSessionId(); + + session = context.getSession(request_session_id); + } + + if (session != null || !create) { + trace("getSession(" + create + "): " + (session != null ? session.getId() : "null")); + + return session; + } + + session = context.createSession(); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + trace("getSession(" + create + "): " + session.getId()); + + return session; + } + + private void setSessionIdCookie() + { + SessionCookieConfig config = context.getSessionCookieConfig(); + + Cookie c = new Cookie(config.getName(), session.getId()); + + c.setComment(config.getComment()); + if (!StringUtil.isBlank(config.getDomain())) { + c.setDomain(config.getDomain()); + } + + c.setHttpOnly(config.isHttpOnly()); + if (!StringUtil.isBlank(config.getPath())) { + c.setPath(config.getPath()); + } + + c.setMaxAge(config.getMaxAge()); + + getResponse(req_info_ptr).addSessionIdCookie(c); + } + + @Override + public Principal getUserPrincipal() + { + log("getUserPrincipal"); + + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() + { + trace("isRequestedSessionIdFromCookie"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_cookie; + } + + @Override + @Deprecated + public boolean isRequestedSessionIdFromUrl() + { + trace("isRequestedSessionIdFromUrl"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdFromURL() + { + trace("isRequestedSessionIdFromURL"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return request_session_id_from_url; + } + + @Override + public boolean isRequestedSessionIdValid() + { + trace("isRequestedSessionIdValid"); + + if (!request_session_id_parsed) { + parseRequestSessionId(); + } + + return context.isSessionIdValid(request_session_id); + } + + @Override + public boolean isUserInRole(String role) + { + log("isUserInRole: " + role); + + return false; + } + + @Override + public void login(String username, String password) throws ServletException + { + log("login: " + username + "," + password); + } + + @Override + public void logout() throws ServletException + { + log("logout"); + } + + + @Override + public AsyncContext getAsyncContext() + { + log("getAsyncContext"); + + return null; + } + + @Override + public Object getAttribute(String name) + { + if (BARE.equals(name)) { + return this; + } + + Object o = attributes.get(name); + + trace("getAttribute: " + name + " = " + o); + + return o; + } + + @Override + public Enumeration<String> getAttributeNames() + { + trace("getAttributeNames"); + + Set<String> names = attributes.keySet(); + return Collections.enumeration(names); + } + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + if (characterEncoding != null) { + return characterEncoding; + } + + getContentType(); + + return characterEncoding; + } + + @Override + public int getContentLength() + { + trace("getContentLength"); + + return (int) getContentLength(req_ptr); + } + + private static native long getContentLength(long req_ptr); + + @Override + public long getContentLengthLong() + { + trace("getContentLengthLong"); + + return getContentLength(req_ptr); + } + + @Override + public String getContentType() + { + trace("getContentType"); + + String content_type = getContentType(req_ptr); + + if (characterEncoding == null && content_type != null) { + MimeTypes.Type mime = MimeTypes.CACHE.get(content_type); + String charset = (mime == null || mime.getCharset() == null) ? MimeTypes.getCharsetFromContentType(content_type) : mime.getCharset().toString(); + if (charset != null) { + characterEncoding = charset; + } + } + + return content_type; + } + + private static native String getContentType(long req_ptr); + + + @Override + public DispatcherType getDispatcherType() + { + trace("getDispatcherType: " + dispatcher_type); + + return dispatcher_type; + } + + @Override + public void setDispatcherType(DispatcherType type) + { + trace("setDispatcherType: " + type); + + dispatcher_type = type; + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + trace("getInputStream"); + + if (reader != null) { + throw new IllegalStateException("getInputStream: getReader() already used"); + } + + if (inputStream == null) { + inputStream = new InputStream(req_info_ptr); + } + + return inputStream; + } + + @Override + public String getLocalAddr() + { + trace("getLocalAddr"); + + return getLocalAddr(req_ptr); + } + + private static native String getLocalAddr(long req_ptr); + + + @Override + public Locale getLocale() + { + log("getLocale"); + + return Locale.getDefault(); + } + + @Override + public Enumeration<Locale> getLocales() + { + log("getLocales"); + + return Collections.emptyEnumeration(); + } + + @Override + public String getLocalName() + { + trace("getLocalName"); + + return getLocalName(req_ptr); + } + + private static native String getLocalName(long req_ptr); + + + @Override + public int getLocalPort() + { + trace("getLocalPort"); + + return getLocalPort(req_ptr); + } + + private static native int getLocalPort(long req_ptr); + + + public MultiMap<String> getParameters() + { + if (parameters != null) { + return parameters; + } + + parameters = new MultiMap<>(); + + String query = getQueryString(); + + if (query != null) { + UrlEncoded.decodeUtf8To(query, parameters); + } + + if (getContentLength() > 0 && + getMethod().equals("POST") && + getContentType().startsWith("application/x-www-form-urlencoded")) + { + try { + UrlEncoded.decodeUtf8To(new InputStream(req_info_ptr), + parameters, getContentLength(), -1); + } catch (IOException e) { + log("Unhandled IOException: " + e); + } + } + + return parameters; + } + + public void setParameters(MultiMap<String> p) + { + parameters = p; + } + + @Override + public String getParameter(String name) + { + trace("getParameter: " + name); + + return getParameters().getValue(name, 0); + } + + @Override + public Map<String,String[]> getParameterMap() + { + trace("getParameterMap"); + + return Collections.unmodifiableMap(getParameters().toStringArrayMap()); + } + + @Override + public Enumeration<String> getParameterNames() + { + trace("getParameterNames"); + + return Collections.enumeration(getParameters().keySet()); + } + + @Override + public String[] getParameterValues(String name) + { + trace("getParameterValues: " + name); + + List<String> vals = getParameters().getValues(name); + if (vals == null) + return null; + return vals.toArray(new String[vals.size()]); + } + + @Override + public String getProtocol() + { + trace("getProtocol"); + + return getProtocol(req_ptr); + } + + private static native String getProtocol(long req_ptr); + + @Override + public BufferedReader getReader() throws IOException + { + trace("getReader"); + + if (inputStream != null) { + throw new IllegalStateException("getReader: getInputStream() already used"); + } + + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(new InputStream(req_info_ptr))); + } + + return reader; + } + + @Override + @Deprecated + public String getRealPath(String path) + { + trace("getRealPath: " + path); + + return context.getRealPath(path); + } + + @Override + public String getRemoteAddr() + { + String res = getRemoteAddr(req_ptr); + + trace("getRemoteAddr: " + res); + + return res; + } + + private static native String getRemoteAddr(long req_ptr); + + + @Override + public String getRemoteHost() + { + String res = getRemoteHost(req_ptr); + + trace("getRemoteHost: " + res); + + return res; + } + + private static native String getRemoteHost(long req_ptr); + + + @Override + public int getRemotePort() + { + int res = getRemotePort(req_ptr); + + trace("getRemotePort: " + res); + + return res; + } + + private static native int getRemotePort(long req_ptr); + + + @Override + public RequestDispatcher getRequestDispatcher(String path) + { + trace("getRequestDispatcher: " + path); + + if (path.startsWith("/")) { + return context.getRequestDispatcher(path); + } + + try { + URI uri = new URI(getRequestURI()); + uri = uri.resolve(path); + + return context.getRequestDispatcher(uri); + } catch (URISyntaxException e) { + log("getRequestDispatcher: failed to create dispatcher: " + e); + } + + return null; + } + + + @Override + public String getScheme() + { + log("getScheme"); + + return getScheme(req_ptr); + } + + private static native String getScheme(long req_ptr); + + + @Override + public String getServerName() + { + String res = getServerName(req_ptr); + + trace("getServerName: " + res); + + return res; + } + + private static native String getServerName(long req_ptr); + + + @Override + public int getServerPort() + { + int res = getServerPort(req_ptr); + + trace("getServerPort: " + res); + + return res; + } + + private static native int getServerPort(long req_ptr); + + @Override + public ServletContext getServletContext() + { + trace("getServletContext"); + + return context; + } + + @Override + public boolean isAsyncStarted() + { + log("isAsyncStarted"); + + return false; + } + + @Override + public boolean isAsyncSupported() + { + log("isAsyncSupported"); + + return false; + } + + @Override + public boolean isSecure() + { + log("isSecure"); + + return false; + } + + @Override + public void removeAttribute(String name) + { + trace("removeAttribute: " + name); + + Object prev = attributes.remove(name); + + if (attr_listener == null || prev == null) { + return; + } + + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + + @Override + public void setAttribute(String name, Object o) + { + trace("setAttribute: " + name + ", " + o); + + Object prev; + + if (o != null) { + prev = attributes.put(name, o); + } else { + prev = attributes.remove(name); + } + + if (attr_listener == null) { + return; + } + + if (prev == null) { + if (o == null) { + return; + } + + attr_listener.attributeAdded(new ServletRequestAttributeEvent( + context, this, name, o)); + } else { + if (o != null) { + attr_listener.attributeReplaced( + new ServletRequestAttributeEvent(context, this, name, prev)); + } else { + attr_listener.attributeRemoved( + new ServletRequestAttributeEvent(context, this, name, prev)); + } + } + } + + public void setAttribute_(String name, Object o) + { + trace("setAttribute_: " + name + ", " + o); + + if (o != null) { + attributes.put(name, o); + } else { + attributes.remove(name); + } + } + + @Override + public void setCharacterEncoding(String env) throws UnsupportedEncodingException + { + trace("setCharacterEncoding: " + env); + + characterEncoding = env; + } + + @Override + public AsyncContext startAsync() throws IllegalStateException + { + log("startAsync"); + + return null; + } + + @Override + public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException + { + log("startAsync(Req, resp)"); + + return null; + } + + @Override + public <T extends HttpUpgradeHandler> T upgrade( + Class<T> httpUpgradeHandlerClass) throws java.io.IOException, ServletException + { + log("upgrade: " + httpUpgradeHandlerClass.getName()); + + return null; + } + + @Override + public String changeSessionId() + { + trace("changeSessionId"); + + getSession(false); + + if (session == null) { + return null; + } + + context.changeSessionId(session); + + if (context.getEffectiveSessionTrackingModes().contains( + SessionTrackingMode.COOKIE)) + { + setSessionIdCookie(); + } + + return session.getId(); + } + + private void log(String msg) + { + msg = "Request." + msg; + log(req_info_ptr, msg, msg.length()); + } + + public static native void log(long req_info_ptr, String msg, int msg_len); + + + private void trace(String msg) + { + msg = "Request." + msg; + trace(req_info_ptr, msg, msg.length()); + } + + public static native void trace(long req_info_ptr, String msg, int msg_len); + + private static native Response getResponse(long req_info_ptr); +} + diff --git a/src/java/nginx/unit/RequestAttrProxy.java b/src/java/nginx/unit/RequestAttrProxy.java new file mode 100644 index 00000000..daedd01a --- /dev/null +++ b/src/java/nginx/unit/RequestAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.ServletRequestAttributeEvent; +import javax.servlet.ServletRequestAttributeListener; + +public class RequestAttrProxy implements ServletRequestAttributeListener +{ + private final List<ServletRequestAttributeListener> listeners_; + + public RequestAttrProxy(List<ServletRequestAttributeListener> listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeAdded(srae); + } + } + + @Override + public void attributeReplaced(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeReplaced(srae); + } + } + + @Override + public void attributeRemoved(ServletRequestAttributeEvent srae) + { + for (ServletRequestAttributeListener l : listeners_) { + l.attributeRemoved(srae); + } + } +} diff --git a/src/java/nginx/unit/Response.java b/src/java/nginx/unit/Response.java new file mode 100644 index 00000000..099d7f15 --- /dev/null +++ b/src/java/nginx/unit/Response.java @@ -0,0 +1,817 @@ +package nginx.unit; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +import java.lang.IllegalArgumentException; +import java.lang.String; + +import java.net.URI; +import java.net.URISyntaxException; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import java.text.SimpleDateFormat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.Locale; +import java.util.TimeZone; +import java.util.Vector; + +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.util.StringUtil; + +public class Response implements HttpServletResponse { + + private long req_info_ptr; + + private static final String defaultCharacterEncoding = "iso-8859-1"; + private String characterEncoding = defaultCharacterEncoding; + private String contentType = null; + private String contentTypeHeader = null; + + private static final Charset ISO_8859_1 = StandardCharsets.ISO_8859_1; + private static final Charset UTF_8 = StandardCharsets.UTF_8; + + private static final String CONTENT_TYPE = "Content-Type"; + private static final byte[] SET_COOKIE_BYTES = "Set-Cookie".getBytes(ISO_8859_1); + private static final byte[] EXPIRES_BYTES = "Expires".getBytes(ISO_8859_1); + + /** + * The only date format permitted when generating HTTP headers. + */ + public static final String RFC1123_DATE = + "EEE, dd MMM yyyy HH:mm:ss zzz"; + + private static final SimpleDateFormat format = + new SimpleDateFormat(RFC1123_DATE, Locale.US); + + private static final String ZERO_DATE_STRING = dateToString(0); + private static final byte[] ZERO_DATE_BYTES = ZERO_DATE_STRING.getBytes(ISO_8859_1); + + /** + * If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie + * will be set as HTTP ONLY. + */ + public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; + + private OutputStream outputStream = null; + + private PrintWriter writer = null; + + + public Response(long ptr) { + req_info_ptr = ptr; + } + + /** + * Format a set cookie value by RFC6265 + * + * @param name the name + * @param value the value + * @param domain the domain + * @param path the path + * @param maxAge the maximum age + * @param isSecure true if secure cookie + * @param isHttpOnly true if for http only + */ + public void addSetRFC6265Cookie( + final String name, + final String value, + final String domain, + final String path, + final long maxAge, + final boolean isSecure, + final boolean isHttpOnly) + { + // Check arguments + if (name == null || name.length() == 0) { + throw new IllegalArgumentException("Bad cookie name"); + } + + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting + // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules + //Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); + // Ensure that Per RFC6265, Cookie.value follows syntax rules + //Syntax.requireValidRFC6265CookieValue(value); + + // Format value and params + StringBuilder buf = new StringBuilder(); + buf.append(name).append('=').append(value == null ? "" : value); + + // Append path + if (path != null && path.length() > 0) { + buf.append(";Path=").append(path); + } + + // Append domain + if (domain != null && domain.length() > 0) { + buf.append(";Domain=").append(domain); + } + + // Handle max-age and/or expires + if (maxAge >= 0) { + // Always use expires + // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies + buf.append(";Expires="); + if (maxAge == 0) + buf.append(ZERO_DATE_STRING); + else + buf.append(dateToString(System.currentTimeMillis() + 1000L * maxAge)); + + buf.append(";Max-Age="); + buf.append(maxAge); + } + + // add the other fields + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + + // add the set cookie + addHeader(req_info_ptr, SET_COOKIE_BYTES, + buf.toString().getBytes(ISO_8859_1)); + + // Expire responses with set-cookie headers so they do not get cached. + setHeader(req_info_ptr, EXPIRES_BYTES, ZERO_DATE_BYTES); + } + + @Override + public void addCookie(Cookie cookie) + { + trace("addCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (StringUtil.isBlank(cookie.getName())) { + throw new IllegalArgumentException("Cookie.name cannot be blank/null"); + } + + if (isCommitted()) { + return; + } + + addCookie_(cookie); + } + + private void addCookie_(Cookie cookie) + { + String comment = cookie.getComment(); + boolean httpOnly = false; + + if (comment != null && comment.contains(HTTP_ONLY_COMMENT)) { + httpOnly = true; + } + + addSetRFC6265Cookie(cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getSecure(), + httpOnly || cookie.isHttpOnly()); + } + + public void addSessionIdCookie(Cookie cookie) + { + trace("addSessionIdCookie: " + cookie.getName() + "=" + cookie.getValue()); + + if (isCommitted()) { + /* + 9.3 The Include Method + + ... any call to HttpServletRequest.getSession() or + HttpServletRequest.getSession(boolean) that would require + adding a Cookie response header must throw an + IllegalStateException if the response has been committed. + */ + throw new IllegalStateException("Response already sent"); + } + + addCookie_(cookie); + } + + @Override + public void addDateHeader(String name, long date) + { + trace("addDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static String dateToString(long date) + { + Date dateValue = new Date(date); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format.format(dateValue); + } + + + @Override + public void addHeader(String name, String value) + { + trace("addHeader: " + name + ": " + value); + + if (value == null) { + return; + } + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + addHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void addHeader(long req_info_ptr, byte[] name, byte[] value); + + + @Override + public void addIntHeader(String name, int value) + { + trace("addIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + addIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void addIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public boolean containsHeader(String name) + { + trace("containsHeader: " + name); + + return containsHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native boolean containsHeader(long req_info_ptr, byte[] name); + + + @Override + @Deprecated + public String encodeRedirectUrl(String url) + { + return encodeRedirectURL(url); + } + + @Override + public String encodeRedirectURL(String url) + { + log("encodeRedirectURL: " + url); + + return url; + } + + @Override + @Deprecated + public String encodeUrl(String url) + { + return encodeURL(url); + } + + @Override + public String encodeURL(String url) + { + log("encodeURL: " + url); + + return url; + } + + @Override + public String getHeader(String name) + { + trace("getHeader: " + name); + + return getHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + } + + private static native String getHeader(long req_info_ptr, byte[] name); + + + @Override + public Collection<String> getHeaderNames() + { + trace("getHeaderNames"); + + Enumeration<String> e = getHeaderNames(req_info_ptr); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration<String> getHeaderNames(long req_info_ptr); + + + @Override + public Collection<String> getHeaders(String name) + { + trace("getHeaders: " + name); + + Enumeration<String> e = getHeaders(req_info_ptr, name.getBytes(ISO_8859_1)); + if (e == null) { + return Collections.emptyList(); + } + + return Collections.list(e); + } + + private static native Enumeration<String> getHeaders(long req_info_ptr, byte[] name); + + + @Override + public int getStatus() + { + trace("getStatus"); + + return getStatus(req_info_ptr); + } + + private static native int getStatus(long req_info_ptr); + + + @Override + public void sendError(int sc) throws IOException + { + sendError(sc, null); + } + + @Override + public void sendError(int sc, String msg) throws IOException + { + trace("sendError: " + sc + ", " + msg); + + if (isCommitted()) { + throw new IllegalStateException("Response already sent"); + } + + setStatus(sc); + + Request request = getRequest(req_info_ptr); + + // If we are allowed to have a body, then produce the error page. + if (sc != SC_NO_CONTENT && sc != SC_NOT_MODIFIED && + sc != SC_PARTIAL_CONTENT && sc >= SC_OK) + { + request.setAttribute_(RequestDispatcher.ERROR_STATUS_CODE, sc); + request.setAttribute_(RequestDispatcher.ERROR_MESSAGE, msg); + request.setAttribute_(RequestDispatcher.ERROR_REQUEST_URI, + request.getRequestURI()); +/* + request.setAttribute_(RequestDispatcher.ERROR_SERVLET_NAME, + request.getServletName()); +*/ + } + +/* + Avoid commit and give chance for error handlers. + + if (!request.isAsyncStarted()) { + commit(); + } +*/ + } + + private static native Request getRequest(long req_info_ptr); + + private void commit() + { + if (writer != null) { + writer.close(); + + } else if (outputStream != null) { + outputStream.close(); + + } else { + commit(req_info_ptr); + } + } + + private static native void commit(long req_info_ptr); + + + @Override + public void sendRedirect(String location) throws IOException + { + trace("sendRedirect: " + location); + + if (isCommitted()) { + return; + } + + try { + URI uri = new URI(location); + + if (!uri.isAbsolute()) { + URI req_uri = new URI(getRequest(req_info_ptr).getRequestURL().toString()); + uri = req_uri.resolve(uri); + + location = uri.toString(); + } + } catch (URISyntaxException e) { + log("sendRedirect: failed to send redirect: " + e); + return; + } + + sendRedirect(req_info_ptr, location.getBytes(ISO_8859_1)); + } + + private static native void sendRedirect(long req_info_ptr, byte[] location); + + + @Override + public void setDateHeader(String name, long date) + { + trace("setDateHeader: " + name + ": " + date); + + if (isCommitted()) { + return; + } + + String value = dateToString(date); + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + + @Override + public void setHeader(String name, String value) + { + trace("setHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + if (CONTENT_TYPE.equalsIgnoreCase(name)) { + setContentType(value); + return; + } + + /* + * When value is null container behaviour is undefined. + * - Tomcat ignores setHeader call; + * - Jetty & Resin acts as removeHeader; + */ + if (value == null) { + removeHeader(req_info_ptr, name.getBytes(ISO_8859_1)); + return; + } + + setHeader(req_info_ptr, name.getBytes(ISO_8859_1), + value.getBytes(ISO_8859_1)); + } + + private static native void setHeader(long req_info_ptr, byte[] name, byte[] value); + + private static native void removeHeader(long req_info_ptr, byte[] name); + + @Override + public void setIntHeader(String name, int value) + { + trace("setIntHeader: " + name + ": " + value); + + if (isCommitted()) { + return; + } + + setIntHeader(req_info_ptr, name.getBytes(ISO_8859_1), value); + } + + private static native void setIntHeader(long req_info_ptr, byte[] name, int value); + + + @Override + public void setStatus(int sc) + { + trace("setStatus: " + sc); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + private static native void setStatus(long req_info_ptr, int sc); + + + @Override + @Deprecated + public void setStatus(int sc, String sm) + { + trace("setStatus: " + sc + "; " + sm); + + if (isCommitted()) { + return; + } + + setStatus(req_info_ptr, sc); + } + + + @Override + public void flushBuffer() throws IOException + { + trace("flushBuffer"); + + if (writer != null) { + writer.flush(); + } + + if (outputStream != null) { + outputStream.flush(); + } + } + + @Override + public int getBufferSize() + { + trace("getBufferSize"); + + return getBufferSize(req_info_ptr); + } + + public static native int getBufferSize(long req_info_ptr); + + + @Override + public String getCharacterEncoding() + { + trace("getCharacterEncoding"); + + return characterEncoding; + } + + @Override + public String getContentType() + { + /* In JIRA decorator get content type called after commit. */ + + String res = contentTypeHeader; + + trace("getContentType: " + res); + + return res; + } + + private static native String getContentType(long req_info_ptr); + + @Override + public Locale getLocale() + { + log("getLocale"); + + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException + { + trace("getOutputStream"); + + if (writer != null) { + throw new IllegalStateException("Writer already created"); + } + + if (outputStream == null) { + outputStream = new OutputStream(req_info_ptr); + } + + return outputStream; + } + + @Override + public PrintWriter getWriter() throws IOException + { + trace("getWriter ( characterEncoding = '" + characterEncoding + "' )"); + + if (outputStream != null) { + throw new IllegalStateException("OutputStream already created"); + } + + if (writer == null) { + ServletOutputStream stream = new OutputStream(req_info_ptr); + + writer = new PrintWriter( + new OutputStreamWriter(stream, Charset.forName(characterEncoding)), + false); + } + + return writer; + } + + @Override + public boolean isCommitted() + { + trace("isCommitted"); + + return isCommitted(req_info_ptr); + } + + public static native boolean isCommitted(long req_info_ptr); + + @Override + public void reset() + { + trace("reset"); + + if (isCommitted()) { + return; + } + + reset(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void reset(long req_info_ptr); + + @Override + public void resetBuffer() + { + trace("resetBuffer"); + + resetBuffer(req_info_ptr); + + writer = null; + outputStream = null; + } + + public static native void resetBuffer(long req_info_ptr); + + @Override + public void setBufferSize(int size) + { + trace("setBufferSize: " + size); + + setBufferSize(req_info_ptr, size); + } + + public static native void setBufferSize(long req_info_ptr, int size); + + @Override + public void setCharacterEncoding(String charset) + { + trace("setCharacterEncoding " + charset); + + if (isCommitted()) { + return; + } + + if (charset == null) { + if (writer != null + && !characterEncoding.equalsIgnoreCase(defaultCharacterEncoding)) + { + /* TODO throw */ + return; + } + + characterEncoding = defaultCharacterEncoding; + } else { + if (writer != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* TODO throw */ + return; + } + + characterEncoding = charset; + } + + if (contentType != null) { + String type = contentType + ";charset=" + characterEncoding; + + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + } + + + @Override + public void setContentLength(int len) + { + trace("setContentLength: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + @Override + public void setContentLengthLong(long len) + { + trace("setContentLengthLong: " + len); + + if (isCommitted()) { + return; + } + + setContentLength(req_info_ptr, len); + } + + private static native void setContentLength(long req_info_ptr, long len); + + + @Override + public void setContentType(String type) + { + trace("setContentType: " + type); + + if (isCommitted()) { + return; + } + + if (type == null) { + removeContentType(req_info_ptr); + contentType = null; + contentTypeHeader = null; + return; + } + + String charset = MimeTypes.getCharsetFromContentType(type); + String ctype = MimeTypes.getContentTypeWithoutCharset(type); + + if (writer != null + && charset != null + && !characterEncoding.equalsIgnoreCase(charset)) + { + /* To late to change character encoding */ + charset = characterEncoding; + type = ctype + ";charset=" + characterEncoding; + } + + if (charset == null) { + type = type + ";charset=" + characterEncoding; + } else { + characterEncoding = charset; + } + + contentType = ctype; + contentTypeHeader = type; + + setContentType(req_info_ptr, type.getBytes(ISO_8859_1)); + } + + private static native void setContentType(long req_info_ptr, byte[] type); + + private static native void removeContentType(long req_info_ptr); + + + @Override + public void setLocale(Locale loc) + { + log("setLocale: " + loc); + } + + private void log(String msg) + { + msg = "Response." + msg; + log(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void log(long req_info_ptr, byte[] msg); + + + private void trace(String msg) + { + msg = "Response." + msg; + trace(req_info_ptr, msg.getBytes(UTF_8)); + } + + public static native void trace(long req_info_ptr, byte[] msg); +} diff --git a/src/java/nginx/unit/Session.java b/src/java/nginx/unit/Session.java new file mode 100644 index 00000000..6be74709 --- /dev/null +++ b/src/java/nginx/unit/Session.java @@ -0,0 +1,251 @@ +package nginx.unit; + +import java.io.Serializable; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; +import javax.servlet.http.HttpSessionBindingListener; + +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Andrey Kazankov + */ +public class Session implements HttpSession, Serializable +{ + private final Map<String, Object> attributes = new HashMap<>(); + private final long creation_time = new Date().getTime(); + private long last_access_time = creation_time; + private long access_time = creation_time; + private int max_inactive_interval; + private String id; + private final Context context; + private boolean is_new = true; + private final HttpSessionAttributeListener attr_listener; + + public Session(Context context, String id, + HttpSessionAttributeListener al, int max_inactive_interval) + { + this.id = id; + this.context = context; + attr_listener = al; + this.max_inactive_interval = max_inactive_interval; + } + + public void setId(String id) + { + this.id = id; + } + + @Override + public long getCreationTime() + { + return creation_time; + } + + @Override + public String getId() + { + return id; + } + + @Override + public long getLastAccessedTime() + { + return last_access_time; + } + + @Override + public ServletContext getServletContext() + { + return context; + } + + @Override + public void setMaxInactiveInterval(int i) + { + max_inactive_interval = i; + } + + @Override + public int getMaxInactiveInterval() + { + return max_inactive_interval; + } + + @Deprecated + @Override + public javax.servlet.http.HttpSessionContext getSessionContext() + { + return null; + } + + @Override + public Object getAttribute(String s) + { + synchronized (attributes) { + return attributes.get(s); + } + } + + @Deprecated + @Override + public Object getValue(String s) + { + return getAttribute(s); + } + + @Override + public Enumeration<String> getAttributeNames() + { + synchronized (attributes) { + return Collections.enumeration(attributes.keySet()); + } + } + + @Deprecated + @Override + public String[] getValueNames() + { + synchronized (attributes) { + return attributes.keySet().toArray(new String[attributes.keySet().size()]); + } + } + + @Override + public void setAttribute(String s, Object o) + { + Object old; + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueBound(e); + } + + synchronized (attributes) { + if (o != null) { + old = attributes.put(s, o); + } else { + old = attributes.remove(s); + } + } + + if (old != null && old instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) old; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null) { + return; + } + + if (o == null) { + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeRemoved(e); + } + + return; + } + + if (old != null) { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, old); + attr_listener.attributeReplaced(e); + } else { + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeAdded(e); + } + } + + @Deprecated + @Override + public void putValue(String s, Object o) + { + setAttribute(s,o); + } + + @Override + public void removeAttribute(String s) + { + Object o; + + synchronized (attributes) { + o = attributes.remove(s); + } + + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s); + + l.valueUnbound(e); + } + + if (attr_listener == null || o == null) { + return; + } + + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, s, o); + attr_listener.attributeRemoved(e); + } + + @Deprecated + @Override + public void removeValue(String s) + { + removeAttribute(s); + } + + @Override + public void invalidate() + { + context.invalidateSession(this); + + unboundAttributes(); + } + + private void unboundAttributes() + { + for (Map.Entry<String, Object> a : attributes.entrySet()) { + Object o = a.getValue(); + if (o != null && o instanceof HttpSessionBindingListener) { + HttpSessionBindingListener l = (HttpSessionBindingListener) o; + HttpSessionBindingEvent e = new HttpSessionBindingEvent(this, a.getKey()); + + l.valueUnbound(e); + } + } + + attributes.clear(); + } + + @Override + public boolean isNew() + { + return is_new; + } + + public void accessed() { + synchronized (this) { + is_new = false; + + last_access_time = access_time; + access_time = new Date().getTime(); + } + } + + public boolean checkTimeOut() + { + return (max_inactive_interval > 0) && + (access_time - last_access_time > max_inactive_interval * 1000); + } +}
\ No newline at end of file diff --git a/src/java/nginx/unit/SessionAttrProxy.java b/src/java/nginx/unit/SessionAttrProxy.java new file mode 100644 index 00000000..bddeede9 --- /dev/null +++ b/src/java/nginx/unit/SessionAttrProxy.java @@ -0,0 +1,40 @@ +package nginx.unit; + +import java.util.List; + +import javax.servlet.http.HttpSessionAttributeListener; +import javax.servlet.http.HttpSessionBindingEvent; + +public class SessionAttrProxy implements HttpSessionAttributeListener +{ + private final List<HttpSessionAttributeListener> listeners_; + + public SessionAttrProxy(List<HttpSessionAttributeListener> listeners) + { + listeners_ = listeners; + } + + @Override + public void attributeAdded(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeAdded(event); + } + } + + @Override + public void attributeRemoved(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeRemoved(event); + } + } + + @Override + public void attributeReplaced(HttpSessionBindingEvent event) + { + for (HttpSessionAttributeListener l : listeners_) { + l.attributeReplaced(event); + } + } +} diff --git a/src/java/nginx/unit/Taglib.java b/src/java/nginx/unit/Taglib.java new file mode 100644 index 00000000..f72033ba --- /dev/null +++ b/src/java/nginx/unit/Taglib.java @@ -0,0 +1,44 @@ +package nginx.unit; + +import javax.servlet.descriptor.TaglibDescriptor; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +public class Taglib implements TaglibDescriptor +{ + private String uri_ = null; + private String location_ = null; + + public Taglib(NodeList nodes) + { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + String tag_name = node.getNodeName(); + + if (tag_name.equals("taglib-uri")) { + uri_ = node.getTextContent().trim(); + continue; + } + + if (tag_name.equals("taglib-location")) { + location_ = node.getTextContent().trim(); + continue; + } + } + + } + + @Override + public String getTaglibURI() + { + return uri_; + } + + @Override + public String getTaglibLocation() + { + return location_; + } +} + diff --git a/src/java/nginx/unit/UnitSessionCookieConfig.java b/src/java/nginx/unit/UnitSessionCookieConfig.java new file mode 100644 index 00000000..e1b2ae04 --- /dev/null +++ b/src/java/nginx/unit/UnitSessionCookieConfig.java @@ -0,0 +1,110 @@ +package nginx.unit; + +import javax.servlet.SessionCookieConfig; + +/* + + <session-config> + <session-timeout>60</session-timeout> + <cookie-config></cookie-config> + <tracking-mode></tracking-mode> + </session-config> + + + */ +public class UnitSessionCookieConfig implements SessionCookieConfig { + + private static final String default_name = "JSESSIONID"; + + private String name = default_name; + private String domain; + private String path; + private String comment; + private boolean httpOnly = true; + private boolean secure = false; + private int maxAge = -1; + + @Override + public void setName(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } + + @Override + public void setDomain(String domain) + { + this.domain = domain; + } + + @Override + public String getDomain() + { + return domain; + } + + @Override + public void setPath(String path) + { + this.path = path; + } + + @Override + public String getPath() + { + return path; + } + + @Override + public void setComment(String comment) + { + this.comment = comment; + } + + @Override + public String getComment() + { + return comment; + } + + @Override + public void setHttpOnly(boolean httpOnly) + { + this.httpOnly = httpOnly; + } + + @Override + public boolean isHttpOnly() + { + return httpOnly; + } + + @Override + public void setSecure(boolean secure) + { + this.secure = secure; + } + + @Override + public boolean isSecure() + { + return secure; + } + + @Override + public void setMaxAge(int maxAge) + { + this.maxAge = maxAge; + } + + @Override + public int getMaxAge() + { + return maxAge; + } +} diff --git a/src/java/nxt_jni.c b/src/java/nxt_jni.c new file mode 100644 index 00000000..02ec1e37 --- /dev/null +++ b/src/java/nxt_jni.c @@ -0,0 +1,175 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <jni.h> +#include <nxt_unit.h> +#include <nxt_unit_field.h> + +#include "nxt_jni.h" + + +static jclass nxt_java_NoSuchElementException_class; +static jclass nxt_java_IOException_class; +static jclass nxt_java_IllegalStateException_class; +static jclass nxt_java_File_class; +static jmethodID nxt_java_File_ctor; + +static inline char nxt_java_lowcase(char c); + + +int +nxt_java_jni_init(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/util/NoSuchElementException"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_NoSuchElementException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/IOException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IOException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/lang/IllegalStateException"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_IllegalStateException_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + cls = (*env)->FindClass(env, "java/io/File"); + if (cls == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + return NXT_UNIT_ERROR; + } + + nxt_java_File_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + + nxt_java_File_ctor = (*env)->GetMethodID(env, nxt_java_File_class, "<init>", + "(Ljava/lang/String;)V"); + if (nxt_java_File_ctor == NULL) { + (*env)->DeleteGlobalRef(env, nxt_java_NoSuchElementException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IOException_class); + (*env)->DeleteGlobalRef(env, nxt_java_IllegalStateException_class); + (*env)->DeleteGlobalRef(env, nxt_java_File_class); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +void +nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_NoSuchElementException_class, msg); +} + + +void +nxt_java_throw_IOException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IOException_class, msg); +} + + +void +nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg) +{ + (*env)->ThrowNew(env, nxt_java_IllegalStateException_class, msg); +} + + +nxt_unit_field_t * +nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *end, + const char *name, uint8_t name_len) +{ + const char *field_name; + + for (/* void */ ; f < end; f++) { + if (f->skip != 0 || f->name_length != name_len) { + continue; + } + + field_name = nxt_unit_sptr_get(&f->name); + + if (nxt_java_strcaseeq(name, field_name, name_len)) { + return f; + } + } + + return NULL; +} + + +int +nxt_java_strcaseeq(const char *str1, const char *str2, int len) +{ + char c1, c2; + const char *end1; + + end1 = str1 + len; + + while (str1 < end1) { + c1 = nxt_java_lowcase(*str1++); + c2 = nxt_java_lowcase(*str2++); + + if (c1 != c2) { + return 0; + } + } + + return 1; +} + + +static inline char +nxt_java_lowcase(char c) +{ + return (c >= 'A' && c <= 'Z') ? c | 0x20 : c; +} + + +jstring +nxt_java_newString(JNIEnv *env, char *str, uint32_t len) +{ + char tmp; + jstring res; + + tmp = str[len]; + + if (tmp != '\0') { + str[len] = '\0'; + } + + res = (*env)->NewStringUTF(env, str); + + if (tmp != '\0') { + str[len] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni.h b/src/java/nxt_jni.h new file mode 100644 index 00000000..62acb752 --- /dev/null +++ b/src/java/nxt_jni.h @@ -0,0 +1,55 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_JNI_H_INCLUDED_ +#define _NXT_JAVA_JNI_H_INCLUDED_ + + +#include <jni.h> +#include <nxt_unit_typedefs.h> + + +int nxt_java_jni_init(JNIEnv *env); + +void nxt_java_throw_NoSuchElementException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IOException(JNIEnv *env, const char *msg); + +void nxt_java_throw_IllegalStateException(JNIEnv *env, const char *msg); + +nxt_unit_field_t *nxt_java_findHeader(nxt_unit_field_t *f, nxt_unit_field_t *e, + const char *name, uint8_t name_len); + +int nxt_java_strcaseeq(const char *str1, const char *str2, int len); + +jstring nxt_java_newString(JNIEnv *env, char *str, uint32_t len); + + +typedef struct { + uint32_t header_size; + uint32_t buf_size; + + jobject jreq; + jobject jresp; + + nxt_unit_buf_t *first; + nxt_unit_buf_t *buf; + +} nxt_java_request_data_t; + + +static inline jlong +nxt_ptr2jlong(void *ptr) +{ + return (jlong) (intptr_t) ptr; +} + +static inline void * +nxt_jlong2ptr(jlong l) +{ + return (void *) (intptr_t) l; +} + +#endif /* _NXT_JAVA_JNI_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Context.c b/src/java/nxt_jni_Context.c new file mode 100644 index 00000000..8f7adddf --- /dev/null +++ b/src/java/nxt_jni_Context.c @@ -0,0 +1,164 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <nxt_unit.h> +#include <jni.h> + +#include "nxt_jni.h" +#include "nxt_jni_Context.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Context_class; +static jmethodID nxt_java_Context_start; +static jmethodID nxt_java_Context_service; +static jmethodID nxt_java_Context_stop; + +static void JNICALL nxt_java_Context_log(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Context_trace(JNIEnv *env, jclass cls, + jlong ctx_ptr, jstring msg, jint msg_len); + + +int +nxt_java_initContext(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Context"); + if (cls == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Context_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Context_class; + + nxt_java_Context_start = (*env)->GetStaticMethodID(env, cls, "start", + "(Ljava/lang/String;[Ljava/net/URL;)Lnginx/unit/Context;"); + if (nxt_java_Context_start == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.start() not found"); + goto failed; + } + + nxt_java_Context_service = (*env)->GetMethodID(env, cls, "service", + "(Lnginx/unit/Request;Lnginx/unit/Response;)V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.service() not found"); + goto failed; + } + + nxt_java_Context_stop = (*env)->GetMethodID(env, cls, "stop", "()V"); + if (nxt_java_Context_service == NULL) { + nxt_unit_warn(NULL, "nginx.unit.Context.stop() not found"); + goto failed; + } + + JNINativeMethod context_methods[] = { + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Context_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Context_class, + context_methods, + sizeof(context_methods) + / sizeof(context_methods[0])); + + nxt_unit_debug(NULL, "registered Context methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Context failed"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_startContext(JNIEnv *env, const char *webapp, jobject classpaths) +{ + jstring webapp_str; + + webapp_str = (*env)->NewStringUTF(env, webapp); + if (webapp_str == NULL) { + return NULL; + } + + return (*env)->CallStaticObjectMethod(env, nxt_java_Context_class, + nxt_java_Context_start, webapp_str, + classpaths); +} + + +void +nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_service, jreq, jresp); +} + + +void +nxt_java_stopContext(JNIEnv *env, jobject ctx) +{ + (*env)->CallVoidMethod(env, ctx, nxt_java_Context_stop); +} + + +static void JNICALL +nxt_java_Context_log(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_log(ctx, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Context_trace(JNIEnv *env, jclass cls, jlong ctx_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_ctx_t *ctx; + + ctx = nxt_jlong2ptr(ctx_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_debug(ctx, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} diff --git a/src/java/nxt_jni_Context.h b/src/java/nxt_jni_Context.h new file mode 100644 index 00000000..976c36cf --- /dev/null +++ b/src/java/nxt_jni_Context.h @@ -0,0 +1,23 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_CONTEXT_H_INCLUDED_ +#define _NXT_JAVA_CONTEXT_H_INCLUDED_ + + +#include <jni.h> + + +int nxt_java_initContext(JNIEnv *env, jobject cl); + +jobject nxt_java_startContext(JNIEnv *env, const char *webapp, + jobject classpaths); + +void nxt_java_service(JNIEnv *env, jobject ctx, jobject jreq, jobject jresp); + +void nxt_java_stopContext(JNIEnv *env, jobject ctx); + +#endif /* _NXT_JAVA_CONTEXT_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.c b/src/java/nxt_jni_HeaderNamesEnumeration.c new file mode 100644 index 00000000..eea0c387 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.c @@ -0,0 +1,153 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <nxt_unit.h> +#include <nxt_unit_request.h> +#include <jni.h> +#include <stdio.h> + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jlong JNICALL nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); +static jstring JNICALL nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong pos); + + +static jclass nxt_java_HeaderNamesEnumeration_class; +static jmethodID nxt_java_HeaderNamesEnumeration_ctor; + + +int +nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeaderNamesEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeaderNamesEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeaderNamesEnumeration_class; + + nxt_java_HeaderNamesEnumeration_ctor = (*env)->GetMethodID(env, cls, + "<init>", "(JJ)V"); + if (nxt_java_HeaderNamesEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod hnenum_methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJ)J", + nxt_java_HeaderNamesEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJ)Ljava/lang/String;", + nxt_java_HeaderNamesEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeaderNamesEnumeration_class, + hnenum_methods, + sizeof(hnenum_methods) + / sizeof(hnenum_methods[0])); + + nxt_unit_debug(NULL, "registered HeaderNamesEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count) +{ + return (*env)->NewObject(env, + nxt_java_HeaderNamesEnumeration_class, + nxt_java_HeaderNamesEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count); +} + + +static jlong JNICALL +nxt_java_HeaderNamesEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos >= size) { + return size; + } + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeaderNamesEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong pos) +{ + char *name, tmp; + jstring res; + nxt_unit_field_t *f; + + f = nxt_jlong2ptr(headers_ptr); + + if (pos > 0) { + while (pos < size + && f[pos].hash == f[pos - 1].hash + && f[pos].name_length == f[pos - 1].name_length) + { + pos++; + } + } + + if (pos >= size) { + nxt_java_throw_NoSuchElementException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + name = nxt_unit_sptr_get(&f->name); + tmp = name[f->name_length]; + + if (tmp != '\0') { + name[f->name_length] = '\0'; + } + + res = (*env)->NewStringUTF(env, name); + + if (tmp != '\0') { + name[f->name_length] = tmp; + } + + return res; +} diff --git a/src/java/nxt_jni_HeaderNamesEnumeration.h b/src/java/nxt_jni_HeaderNamesEnumeration.h new file mode 100644 index 00000000..90df8f54 --- /dev/null +++ b/src/java/nxt_jni_HeaderNamesEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ + + +#include <jni.h> +#include <nxt_unit_typedefs.h> + + +int nxt_java_initHeaderNamesEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeaderNamesEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count); + +#endif /* _NXT_JAVA_HEADERNAMESENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_HeadersEnumeration.c b/src/java/nxt_jni_HeadersEnumeration.c new file mode 100644 index 00000000..aaea710d --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.c @@ -0,0 +1,148 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <nxt_unit.h> +#include <nxt_unit_request.h> +#include <jni.h> +#include <stdio.h> + +#include "nxt_jni.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" + + +static jclass nxt_java_HeadersEnumeration_class; +static jmethodID nxt_java_HeadersEnumeration_ctor; + + +static jlong JNICALL nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + +static jstring JNICALL nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, + jclass cls, jlong headers_ptr, jlong size, jlong ipos, jlong pos); + + +int +nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.HeadersEnumeration"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_HeadersEnumeration_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_HeadersEnumeration_class; + + nxt_java_HeadersEnumeration_ctor = (*env)->GetMethodID(env, cls, + "<init>", "(JJJ)V"); + if (nxt_java_HeadersEnumeration_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod methods[] = { + { (char *) "nextElementPos", + (char *) "(JJJJ)J", + nxt_java_HeadersEnumeration_nextElementPos }, + + { (char *) "nextElement", + (char *) "(JJJJ)Ljava/lang/String;", + nxt_java_HeadersEnumeration_nextElement }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_HeadersEnumeration_class, + methods, + sizeof(methods) / sizeof(methods[0])); + + nxt_unit_debug(NULL, "registered HeadersEnumeration methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos) +{ + return (*env)->NewObject(env, + nxt_java_HeadersEnumeration_class, + nxt_java_HeadersEnumeration_ctor, nxt_ptr2jlong(f), + (jlong) fields_count, (jlong) pos); +} + + +static jlong JNICALL +nxt_java_HeadersEnumeration_nextElementPos(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + return size; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + return size; + } + + if (!nxt_java_strcaseeq(nxt_unit_sptr_get(&f->name), + nxt_unit_sptr_get(&init_field->name), + init_field->name_length)) + { + return size; + } + + return pos; +} + + +static jstring JNICALL +nxt_java_HeadersEnumeration_nextElement(JNIEnv *env, jclass cls, + jlong headers_ptr, jlong size, jlong ipos, jlong pos) +{ + nxt_unit_field_t *f, *init_field; + + f = nxt_jlong2ptr(headers_ptr); + + init_field = f + ipos; + + if (pos >= size) { + nxt_java_throw_IOException(env, "pos >= size"); + + return NULL; + } + + f += pos; + + if (f->hash != init_field->hash + || f->name_length != init_field->name_length) + { + nxt_java_throw_IOException(env, "f->hash != hash"); + + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} diff --git a/src/java/nxt_jni_HeadersEnumeration.h b/src/java/nxt_jni_HeadersEnumeration.h new file mode 100644 index 00000000..10f9393c --- /dev/null +++ b/src/java/nxt_jni_HeadersEnumeration.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ +#define _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ + + +#include <jni.h> +#include <nxt_unit_typedefs.h> + + +int nxt_java_initHeadersEnumeration(JNIEnv *env, jobject cl); + +jobject nxt_java_newHeadersEnumeration(JNIEnv *env, nxt_unit_field_t *f, + uint32_t fields_count, uint32_t pos); + +#endif /* _NXT_JAVA_HEADERSENUMERATION_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_InputStream.c b/src/java/nxt_jni_InputStream.c new file mode 100644 index 00000000..b96ff742 --- /dev/null +++ b/src/java/nxt_jni_InputStream.c @@ -0,0 +1,230 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <jni.h> +#include <nxt_unit.h> +#include <string.h> + +#include "nxt_jni.h" +#include "nxt_jni_InputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jint JNICALL nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jboolean JNICALL nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static jint JNICALL nxt_java_InputStream_read(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static jlong JNICALL nxt_java_InputStream_skip(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong n); +static jint JNICALL nxt_java_InputStream_available(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_InputStream_class; + + +int +nxt_java_initInputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.InputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_InputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + JNINativeMethod is_methods[] = { + { (char *) "readLine", + (char *) "(J[BII)I", + nxt_java_InputStream_readLine }, + + { (char *) "isFinished", + (char *) "(J)Z", + nxt_java_InputStream_isFinished }, + + { (char *) "read", + (char *) "(J)I", + nxt_java_InputStream_readByte }, + + { (char *) "read", + (char *) "(J[BII)I", + nxt_java_InputStream_read }, + + { (char *) "skip", + (char *) "(JJ)J", + nxt_java_InputStream_skip }, + + { (char *) "available", + (char *) "(J)I", + nxt_java_InputStream_available }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_InputStream_class, + is_methods, + sizeof(is_methods) / sizeof(is_methods[0])); + + nxt_unit_debug(NULL, "registered InputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static jint JNICALL +nxt_java_InputStream_readLine(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray out, jint off, jint len) +{ + char *p; + jint size, b_size; + uint8_t *data; + ssize_t res; + nxt_unit_buf_t *b; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = 0; + + for (b = req->content_buf; b; b = nxt_unit_buf_next(b)) { + b_size = b->end - b->free; + p = memchr(b->free, '\n', b_size); + + if (p != NULL) { + p++; + size += p - b->free; + break; + } + + size += b_size; + + if (size >= len) { + break; + } + } + + len = len < size ? len : size; + + data = (*env)->GetPrimitiveArrayCritical(env, out, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "readLine '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, out, data, 0); + + return res > 0 ? res : -1; +} + + +static jboolean JNICALL +nxt_java_InputStream_isFinished(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length == 0; +} + + +static jint JNICALL +nxt_java_InputStream_readByte(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + uint8_t b; + ssize_t size; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + size = nxt_unit_request_read(req, &b, 1); + + return size == 1 ? b : -1; +} + + +static jint JNICALL +nxt_java_InputStream_read(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + uint8_t *data; + ssize_t res; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + data = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + res = nxt_unit_request_read(req, data + off, len); + + nxt_unit_req_debug(req, "read '%.*s'", res, (char *) data + off); + + (*env)->ReleasePrimitiveArrayCritical(env, b, data, 0); + + return res > 0 ? res : -1; +} + + +static jlong JNICALL +nxt_java_InputStream_skip(JNIEnv *env, jclass cls, jlong req_info_ptr, jlong n) +{ + size_t rest, b_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + rest = n; + + buf = req->content_buf; + + while (buf != NULL) { + b_size = buf->end - buf->free; + b_size = rest < b_size ? rest : b_size; + + buf->free += b_size; + rest -= b_size; + + if (rest == 0) { + if (buf->end == buf->free) { + buf = nxt_unit_buf_next(buf); + } + + break; + } + + buf = nxt_unit_buf_next(buf); + } + + n = n < (jlong) req->content_length ? n : (jlong) req->content_length; + + req->content_length -= n; + + return n; +} + + +static jint JNICALL +nxt_java_InputStream_available(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + return req->content_length; +} diff --git a/src/java/nxt_jni_InputStream.h b/src/java/nxt_jni_InputStream.h new file mode 100644 index 00000000..b20b262a --- /dev/null +++ b/src/java/nxt_jni_InputStream.h @@ -0,0 +1,15 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ + + +#include <jni.h> + + +int nxt_java_initInputStream(JNIEnv *env, jobject cl); + +#endif /* _NXT_JAVA_INPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_OutputStream.c b/src/java/nxt_jni_OutputStream.c new file mode 100644 index 00000000..170b33ba --- /dev/null +++ b/src/java/nxt_jni_OutputStream.c @@ -0,0 +1,236 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <jni.h> +#include <nxt_unit.h> + +#include "nxt_jni.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static void JNICALL nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint b); +static nxt_unit_buf_t *nxt_java_OutputStream_req_buf(JNIEnv *env, + nxt_unit_request_info_t *req); +static void JNICALL nxt_java_OutputStream_write(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray b, jint off, jint len); +static void JNICALL nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, + jlong req_info_ptr); +static void JNICALL nxt_java_OutputStream_close(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_OutputStream_class; + + +int +nxt_java_initOutputStream(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.OutputStream"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_OutputStream_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + + cls = nxt_java_OutputStream_class; + + JNINativeMethod os_methods[] = { + { (char *) "write", + (char *) "(JI)V", + nxt_java_OutputStream_writeByte }, + + { (char *) "write", + (char *) "(J[BII)V", + nxt_java_OutputStream_write }, + + { (char *) "flush", + (char *) "(J)V", + nxt_java_OutputStream_flush }, + + { (char *) "close", + (char *) "(J)V", + nxt_java_OutputStream_close }, + }; + + res = (*env)->RegisterNatives(env, nxt_java_OutputStream_class, + os_methods, + sizeof(os_methods) / sizeof(os_methods[0])); + + nxt_unit_debug(NULL, "registered OutputStream methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_OutputStream_writeByte(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint b) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + *buf->free++ = b; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +int +nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + int rc; + nxt_java_request_data_t *data; + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + rc = nxt_unit_response_init(req, 200, 0, 0); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to allocate response"); + + return rc; + } + } + + if (!nxt_unit_response_is_sent(req)) { + rc = nxt_unit_response_send(req); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send response headers"); + + return rc; + } + } + + if (data->buf != NULL) { + rc = nxt_unit_buf_send(data->buf); + if (rc != NXT_UNIT_OK) { + nxt_java_throw_IOException(env, "Failed to send buffer"); + + } else { + data->buf = NULL; + } + + } else { + rc = NXT_UNIT_OK; + } + + return rc; +} + + +static nxt_unit_buf_t * +nxt_java_OutputStream_req_buf(JNIEnv *env, nxt_unit_request_info_t *req) +{ + uint32_t size; + nxt_unit_buf_t *buf; + nxt_java_request_data_t *data; + + data = req->data; + buf = data->buf; + + if (buf == NULL || buf->free >= buf->end) { + size = data->buf_size == 0 ? nxt_unit_buf_min() : data->buf_size; + + buf = nxt_unit_response_buf_alloc(req, size); + if (buf == NULL) { + nxt_java_throw_IOException(env, "Failed to allocate buffer"); + + return NULL; + } + + data->buf = buf; + } + + return buf; +} + + +static void JNICALL +nxt_java_OutputStream_write(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray b, jint off, jint len) +{ + int rc; + jint copy; + uint8_t *ptr; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + ptr = (*env)->GetPrimitiveArrayCritical(env, b, NULL); + + while (len > 0) { + buf = nxt_java_OutputStream_req_buf(env, req); + if (buf == NULL) { + return; + } + + copy = buf->end - buf->free; + copy = copy < len ? copy : len; + + memcpy(buf->free, ptr + off, copy); + buf->free += copy; + + len -= copy; + off += copy; + + if ((uint32_t) (buf->free - buf->start) >= data->buf_size) { + rc = nxt_java_OutputStream_flush_buf(env, req); + if (rc != NXT_UNIT_OK) { + break; + } + } + } + + (*env)->ReleasePrimitiveArrayCritical(env, b, ptr, 0); +} + + +static void JNICALL +nxt_java_OutputStream_flush(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_OutputStream_flush_buf(env, req); + } +} + + +static void JNICALL +nxt_java_OutputStream_close(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_OutputStream_flush_buf(env, nxt_jlong2ptr(req_info_ptr)); +} diff --git a/src/java/nxt_jni_OutputStream.h b/src/java/nxt_jni_OutputStream.h new file mode 100644 index 00000000..0c3c9989 --- /dev/null +++ b/src/java/nxt_jni_OutputStream.h @@ -0,0 +1,17 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ +#define _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ + + +#include <jni.h> + + +int nxt_java_initOutputStream(JNIEnv *env, jobject cl); + +int nxt_java_OutputStream_flush_buf(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_OUTPUTSTREAM_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Request.c b/src/java/nxt_jni_Request.c new file mode 100644 index 00000000..6fb9cb44 --- /dev/null +++ b/src/java/nxt_jni_Request.c @@ -0,0 +1,658 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <nxt_unit.h> +#include <nxt_unit_request.h> +#include <jni.h> +#include <stdio.h> +#include <stdlib.h> + +#include "nxt_jni.h" +#include "nxt_jni_Request.h" +#include "nxt_jni_URLClassLoader.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" + + +static jstring JNICALL nxt_java_Request_getHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jobject JNICALL nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, + jlong req_ptr); +static jobject JNICALL nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jint JNICALL nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, + jlong req_ptr, jstring name, jint name_len); +static jstring JNICALL nxt_java_Request_getMethod(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, + jlong req_ptr); +static jlong JNICALL nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getContentType(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getScheme(JNIEnv *env, jclass cls, + jlong req_ptr); +static jstring JNICALL nxt_java_Request_getServerName(JNIEnv *env, jclass cls, + jlong req_ptr); +static jint JNICALL nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, + jlong req_ptr); +static void JNICALL nxt_java_Request_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static void JNICALL nxt_java_Request_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jstring msg, jint msg_len); +static jobject JNICALL nxt_java_Request_getResponse(JNIEnv *env, jclass cls, + jlong req_info_ptr); + + +static jclass nxt_java_Request_class; +static jmethodID nxt_java_Request_ctor; + + +int +nxt_java_initRequest(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Request"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Request_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Request_class; + + nxt_java_Request_ctor = (*env)->GetMethodID(env, cls, "<init>", "(Lnginx/unit/Context;JJ)V"); + if (nxt_java_Request_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod request_methods[] = { + { (char *) "getHeader", + (char *) "(JLjava/lang/String;I)Ljava/lang/String;", + nxt_java_Request_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Request_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(JLjava/lang/String;I)Ljava/util/Enumeration;", + nxt_java_Request_getHeaders }, + + { (char *) "getIntHeader", + (char *) "(JLjava/lang/String;I)I", + nxt_java_Request_getIntHeader }, + + { (char *) "getMethod", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getMethod }, + + { (char *) "getQueryString", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getQueryString }, + + { (char *) "getRequestURI", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRequestURI }, + + { (char *) "getContentLength", + (char *) "(J)J", + nxt_java_Request_getContentLength }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getContentType }, + + { (char *) "getLocalAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalAddr }, + + { (char *) "getLocalName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getLocalName }, + + { (char *) "getLocalPort", + (char *) "(J)I", + nxt_java_Request_getLocalPort }, + + { (char *) "getProtocol", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getProtocol }, + + { (char *) "getRemoteAddr", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteAddr }, + + { (char *) "getRemoteHost", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getRemoteHost }, + + { (char *) "getRemotePort", + (char *) "(J)I", + nxt_java_Request_getRemotePort }, + + { (char *) "getScheme", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getScheme }, + + { (char *) "getServerName", + (char *) "(J)Ljava/lang/String;", + nxt_java_Request_getServerName }, + + { (char *) "getServerPort", + (char *) "(J)I", + nxt_java_Request_getServerPort }, + + { (char *) "log", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_log }, + + { (char *) "trace", + (char *) "(JLjava/lang/String;I)V", + nxt_java_Request_trace }, + + { (char *) "getResponse", + (char *) "(J)Lnginx/unit/Response;", + nxt_java_Request_getResponse }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Request_class, + request_methods, + sizeof(request_methods) / sizeof(request_methods[0])); + + nxt_unit_debug(NULL, "registered Request methods: %d", res); + + if (res != 0) { + nxt_unit_warn(NULL, "registering natives for Request failed"); + goto failed; + } + + res = nxt_java_initHeadersEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + res = nxt_java_initHeaderNamesEnumeration(env, cl); + if (res != NXT_UNIT_OK) { + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Request_class, + nxt_java_Request_ctor, ctx, nxt_ptr2jlong(req), + nxt_ptr2jlong(req->request)); +} + + +static jstring JNICALL +nxt_java_Request_getHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return NULL; + } + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); +} + + +static jobject JNICALL +nxt_java_Request_getHeaderNames(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newHeaderNamesEnumeration(env, r->fields, r->fields_count); +} + + +static jobject JNICALL +nxt_java_Request_getHeaders(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return NULL; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + f = r->fields + r->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, r->fields, r->fields_count, + f - r->fields); +} + + +static jint JNICALL +nxt_java_Request_getIntHeader(JNIEnv *env, jclass cls, jlong req_ptr, + jstring name, jint name_len) +{ + jint res; + char *value, *end; + const char *name_str; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + res = -1; + + name_str = (*env)->GetStringUTFChars(env, name, NULL); + if (name_str == NULL) { + return res; + } + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + name_str, name_len); + + (*env)->ReleaseStringUTFChars(env, name, name_str); + + if (f == NULL) { + return res; + } + + value = nxt_unit_sptr_get(&f->value); + end = value + f->value_length; + + res = strtol(value, &end, 10); + + if (end < value + f->value_length) { + // TODO throw NumberFormatException.forInputString(value) + } + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getMethod(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->method)); +} + + +static jstring JNICALL +nxt_java_Request_getQueryString(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return (*env)->NewStringUTF(env, query); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getRequestURI(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *target, *query; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + target = nxt_unit_sptr_get(&r->target); + + if (r->query.offset != 0) { + query = nxt_unit_sptr_get(&r->query); + return nxt_java_newString(env, target, query - target - 1); + } + + return (*env)->NewStringUTF(env, target); +} + + +static jlong JNICALL +nxt_java_Request_getContentLength(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return r->content_length; +} + + +static jstring JNICALL +nxt_java_Request_getContentType(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + if (r->content_type_field != NXT_UNIT_NONE_FIELD) { + f = r->fields + r->content_type_field; + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&f->value)); + } + + return NULL; +} + + +static jstring JNICALL +nxt_java_Request_getLocalAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->local), + r->local_length); +} + + +static jstring JNICALL +nxt_java_Request_getLocalName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *local, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + colon = local + r->local_length; + } + + return nxt_java_newString(env, local, colon - local); +} + + +static jint JNICALL +nxt_java_Request_getLocalPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *local, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + local = nxt_unit_sptr_get(&r->local); + colon = memchr(local, ':', r->local_length); + + if (colon == NULL) { + return 80; + } + + tmp = local[r->local_length]; + + local[r->local_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + local[r->local_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getProtocol(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return (*env)->NewStringUTF(env, nxt_unit_sptr_get(&r->version)); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteAddr(JNIEnv *env, jclass cls, jlong req_ptr) +{ + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + return nxt_java_newString(env, nxt_unit_sptr_get(&r->remote), + r->remote_length); +} + + +static jstring JNICALL +nxt_java_Request_getRemoteHost(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *remote, *colon; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + colon = remote + r->remote_length; + } + + return nxt_java_newString(env, remote, colon - remote); +} + + +static jint JNICALL +nxt_java_Request_getRemotePort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *remote, *colon, tmp; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + remote = nxt_unit_sptr_get(&r->remote); + colon = memchr(remote, ':', r->remote_length); + + if (colon == NULL) { + return 80; + } + + tmp = remote[r->remote_length]; + + remote[r->remote_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + remote[r->remote_length] = tmp; + + return res; +} + + +static jstring JNICALL +nxt_java_Request_getScheme(JNIEnv *env, jclass cls, jlong req_ptr) +{ + return (*env)->NewStringUTF(env, "http"); +} + + +static jstring JNICALL +nxt_java_Request_getServerName(JNIEnv *env, jclass cls, jlong req_ptr) +{ + char *host, *colon; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + colon = host + f->value_length; + } + + return nxt_java_newString(env, host, colon - host); + } + + return nxt_java_Request_getLocalName(env, cls, req_ptr); +} + + +static jint JNICALL +nxt_java_Request_getServerPort(JNIEnv *env, jclass cls, jlong req_ptr) +{ + jint res; + char *host, *colon, tmp; + nxt_unit_field_t *f; + nxt_unit_request_t *r; + + r = nxt_jlong2ptr(req_ptr); + + f = nxt_java_findHeader(r->fields, r->fields + r->fields_count, + "Host", 4); + if (f != NULL) { + host = nxt_unit_sptr_get(&f->value); + + colon = memchr(host, ':', f->value_length); + + if (colon == NULL) { + return 80; + } + + tmp = host[f->value_length]; + + host[f->value_length] = '\0'; + + res = strtol(colon + 1, NULL, 10); + + host[f->value_length] = tmp; + + return res; + } + + return nxt_java_Request_getLocalPort(env, cls, req_ptr); +} + + +static void JNICALL +nxt_java_Request_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +} + + +static void JNICALL +nxt_java_Request_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jstring msg, + jint msg_len) +{ +#if (NXT_DEBUG) + const char *msg_str; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + msg_str = (*env)->GetStringUTFChars(env, msg, NULL); + if (msg_str == NULL) { + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleaseStringUTFChars(env, msg, msg_str); +#endif +} + + +static jobject JNICALL +nxt_java_Request_getResponse(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jresp; +} diff --git a/src/java/nxt_jni_Request.h b/src/java/nxt_jni_Request.h new file mode 100644 index 00000000..1c9c1428 --- /dev/null +++ b/src/java/nxt_jni_Request.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_REQUEST_H_INCLUDED_ +#define _NXT_JAVA_REQUEST_H_INCLUDED_ + + +#include <jni.h> +#include <nxt_unit_typedefs.h> + + +int nxt_java_initRequest(JNIEnv *env, jobject cl); + +jobject nxt_java_newRequest(JNIEnv *env, jobject ctx, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_REQUEST_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Response.c b/src/java/nxt_jni_Response.c new file mode 100644 index 00000000..2ccfd854 --- /dev/null +++ b/src/java/nxt_jni_Response.c @@ -0,0 +1,1105 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_auto_config.h> + +#include <nxt_unit.h> +#include <nxt_unit_response.h> +#include <jni.h> +#include <stdio.h> + +#include "nxt_jni.h" +#include "nxt_jni_Response.h" +#include "nxt_jni_HeadersEnumeration.h" +#include "nxt_jni_HeaderNamesEnumeration.h" +#include "nxt_jni_OutputStream.h" +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_Response_class; +static jmethodID nxt_java_Response_ctor; + + +static void JNICALL nxt_java_Response_addHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static nxt_unit_request_info_t *nxt_java_get_response_info( + jlong req_info_ptr, uint32_t extra_fields, uint32_t extra_data); + +static void JNICALL nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void nxt_java_add_int_header(nxt_unit_request_info_t *req, + const char *name, uint8_t name_len, int value); + +static jboolean JNICALL nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name); + +static jstring JNICALL nxt_java_Response_getHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jobject JNICALL nxt_java_Response_getHeaderNames(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static jint JNICALL nxt_java_Response_getStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static jobject JNICALL nxt_java_Response_getRequest(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_commit(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc); + +static int nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len); + +static void JNICALL nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value); + +static void JNICALL nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name); + +static int nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len); + +static void JNICALL nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value); + +static void JNICALL nxt_java_Response_setStatus(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint sc); + +static jstring JNICALL nxt_java_Response_getContentType(JNIEnv *env, + jclass cls, jlong req_info_ptr); + +static jboolean JNICALL nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_reset(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr, jint size); + +static jint JNICALL nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, + jlong req_info_ptr, jlong len); + +static void JNICALL nxt_java_Response_setContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray type); + +static void JNICALL nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, + jlong req_info_ptr); + +static void JNICALL nxt_java_Response_log(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +static void JNICALL nxt_java_Response_trace(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray msg); + +int +nxt_java_initResponse(JNIEnv *env, jobject cl) +{ + int res; + jclass cls; + + cls = nxt_java_loadClass(env, cl, "nginx.unit.Response"); + if (cls == NULL) { + return NXT_UNIT_ERROR; + } + + nxt_java_Response_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Response_class; + + nxt_java_Response_ctor = (*env)->GetMethodID(env, cls, "<init>", "(J)V"); + if (nxt_java_Response_ctor == NULL) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + JNINativeMethod resp_methods[] = { + { (char *) "addHeader", + (char *) "(J[B[B)V", + nxt_java_Response_addHeader }, + + { (char *) "addIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_addIntHeader }, + + { (char *) "containsHeader", + (char *) "(J[B)Z", + nxt_java_Response_containsHeader }, + + { (char *) "getHeader", + (char *) "(J[B)Ljava/lang/String;", + nxt_java_Response_getHeader }, + + { (char *) "getHeaderNames", + (char *) "(J)Ljava/util/Enumeration;", + nxt_java_Response_getHeaderNames }, + + { (char *) "getHeaders", + (char *) "(J[B)Ljava/util/Enumeration;", + nxt_java_Response_getHeaders }, + + { (char *) "getStatus", + (char *) "(J)I", + nxt_java_Response_getStatus }, + + { (char *) "getRequest", + (char *) "(J)Lnginx/unit/Request;", + nxt_java_Response_getRequest }, + + { (char *) "commit", + (char *) "(J)V", + nxt_java_Response_commit }, + + { (char *) "sendRedirect", + (char *) "(J[B)V", + nxt_java_Response_sendRedirect }, + + { (char *) "setHeader", + (char *) "(J[B[B)V", + nxt_java_Response_setHeader }, + + { (char *) "removeHeader", + (char *) "(J[B)V", + nxt_java_Response_removeHeader }, + + { (char *) "setIntHeader", + (char *) "(J[BI)V", + nxt_java_Response_setIntHeader }, + + { (char *) "setStatus", + (char *) "(JI)V", + nxt_java_Response_setStatus }, + + { (char *) "getContentType", + (char *) "(J)Ljava/lang/String;", + nxt_java_Response_getContentType }, + + { (char *) "isCommitted", + (char *) "(J)Z", + nxt_java_Response_isCommitted }, + + { (char *) "reset", + (char *) "(J)V", + nxt_java_Response_reset }, + + { (char *) "resetBuffer", + (char *) "(J)V", + nxt_java_Response_resetBuffer }, + + { (char *) "setBufferSize", + (char *) "(JI)V", + nxt_java_Response_setBufferSize }, + + { (char *) "getBufferSize", + (char *) "(J)I", + nxt_java_Response_getBufferSize }, + + { (char *) "setContentLength", + (char *) "(JJ)V", + nxt_java_Response_setContentLength }, + + { (char *) "setContentType", + (char *) "(J[B)V", + nxt_java_Response_setContentType }, + + { (char *) "removeContentType", + (char *) "(J)V", + nxt_java_Response_removeContentType }, + + { (char *) "log", + (char *) "(J[B)V", + nxt_java_Response_log }, + + { (char *) "trace", + (char *) "(J[B)V", + nxt_java_Response_trace }, + + }; + + res = (*env)->RegisterNatives(env, nxt_java_Response_class, + resp_methods, + sizeof(resp_methods) + / sizeof(resp_methods[0])); + + nxt_unit_debug(NULL, "registered Response methods: %d", res); + + if (res != 0) { + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; + } + + return NXT_UNIT_OK; +} + + +jobject +nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req) +{ + return (*env)->NewObject(env, nxt_java_Response_class, + nxt_java_Response_ctor, nxt_ptr2jlong(req)); +} + + +static void JNICALL +nxt_java_Response_addHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + nxt_unit_req_warn(req, "addHeader: failed to get value content"); + + return; + } + + rc = nxt_unit_response_add_field(req, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static nxt_unit_request_info_t * +nxt_java_get_response_info(jlong req_info_ptr, uint32_t extra_fields, + uint32_t extra_data) +{ + int rc; + char *p; + uint32_t max_size; + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return NULL; + } + + data = req->data; + + if (!nxt_unit_response_is_init(req)) { + max_size = nxt_unit_buf_max(); + max_size = max_size < data->header_size ? max_size : data->header_size; + + rc = nxt_unit_response_init(req, 200, 16, max_size); + if (rc != NXT_UNIT_OK) { + return NULL; + } + } + + buf = req->response_buf; + + if (extra_fields > req->response_max_fields + - req->response->fields_count + || extra_data > (uint32_t) (buf->end - buf->free)) + { + p = buf->start + req->response_max_fields * sizeof(nxt_unit_field_t); + + max_size = 2 * (buf->end - p); + if (max_size > nxt_unit_buf_max()) { + nxt_unit_req_warn(req, "required max_size is too big: %"PRIu32, + max_size); + return NULL; + } + + rc = nxt_unit_response_realloc(req, 2 * req->response_max_fields, + max_size); + if (rc != NXT_UNIT_OK) { + nxt_unit_req_warn(req, "reallocation failed: %"PRIu32", %"PRIu32, + 2 * req->response_max_fields, max_size); + return NULL; + } + } + + return req; +} + + +static void JNICALL +nxt_java_Response_addIntHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name, jint value) +{ + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + 40); + if (req == NULL) { + return; + } + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "addIntHeader: failed to get name content"); + return; + } + + nxt_java_add_int_header(req, name_str, name_len, value); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void +nxt_java_add_int_header(nxt_unit_request_info_t *req, const char *name, + uint8_t name_len, int value) +{ + char *p; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + + resp = req->response; + + f = resp->fields + resp->fields_count; + p = req->response_buf->free; + + f->hash = nxt_unit_field_hash(name, name_len); + f->skip = 0; + f->name_length = name_len; + + nxt_unit_sptr_set(&f->name, p); + memcpy(p, name, name_len); + p += name_len; + + nxt_unit_sptr_set(&f->value, p); + f->value_length = snprintf(p, 40, "%d", (int) value); + p += f->value_length + 1; + + resp->fields_count++; + req->response_buf->free = p; + +} + + +static jboolean JNICALL +nxt_java_Response_containsHeader(JNIEnv *env, + jclass cls, jlong req_info_ptr, jarray name) +{ + jboolean res; + char *name_str; + jsize name_len; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "containsHeader: response is not initialized"); + return 0; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "containsHeader: response already sent"); + return 0; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "containsHeader: failed to get name content"); + return 0; + } + + resp = req->response; + + res = nxt_java_findHeader(resp->fields, + resp->fields + resp->fields_count, + name_str, name_len) != NULL; + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + return res; +} + + +static jstring JNICALL +nxt_java_Response_getHeader(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeader: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeader: response already sent"); + return NULL; + } + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeader: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jobject JNICALL +nxt_java_Response_getHeaderNames(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaderNames: response already sent"); + return NULL; + } + + return nxt_java_newHeaderNamesEnumeration(env, req->response->fields, + req->response->fields_count); +} + + +static jobject JNICALL +nxt_java_Response_getHeaders(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + char *name_str; + jsize name_len; + nxt_unit_field_t *f; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getHeaders: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getHeaders: response already sent"); + return NULL; + } + + resp = req->response; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(req, "getHeaders: failed to get name content"); + return NULL; + } + + f = nxt_java_findHeader(resp->fields, resp->fields + resp->fields_count, + name_str, name_len); + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + if (f == NULL) { + f = resp->fields + resp->fields_count; + } + + return nxt_java_newHeadersEnumeration(env, resp->fields, resp->fields_count, + f - resp->fields); +} + + +static jint JNICALL +nxt_java_Response_getStatus(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getStatus: response is not initialized"); + return 200; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getStatus: response already sent"); + return 200; + } + + return req->response->status; +} + + +static jobject JNICALL +nxt_java_Response_getRequest(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->jreq; +} + + +static void JNICALL +nxt_java_Response_commit(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + nxt_java_OutputStream_flush_buf(env, req); +} + + +static void JNICALL +nxt_java_Response_sendRedirect(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray loc) +{ + int rc; + char *loc_str; + jsize loc_len; + nxt_unit_request_info_t *req; + + static const char location[] = "Location"; + static const uint32_t location_len = sizeof(location) - 1; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + loc_len = (*env)->GetArrayLength(env, loc); + + req = nxt_java_get_response_info(req_info_ptr, 1, + location_len + loc_len + 2); + if (req == NULL) { + return; + } + + loc_str = (*env)->GetPrimitiveArrayCritical(env, loc, NULL); + if (loc_str == NULL) { + nxt_unit_req_warn(req, "sendRedirect: failed to get loc content"); + return; + } + + req->response->status = 302; + + rc = nxt_java_response_set_header(req_info_ptr, location, location_len, + loc_str, loc_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, loc, loc_str, 0); + + nxt_unit_response_send(req); +} + + +static int +nxt_java_response_set_header(jlong req_info_ptr, + const char *name, jint name_len, const char *value, jint value_len) +{ + int add_field; + char *dst; + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + add_field = 1; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + if (add_field && f->value_length >= (uint32_t) value_len) { + dst = nxt_unit_sptr_get(&f->value); + memcpy(dst, value, value_len); + dst[value_len] = '\0'; + f->value_length = value_len; + + add_field = 0; + f->skip = 0; + + } else { + f->skip = 1; + } + + ++f; + } + + if (!add_field) { + return NXT_UNIT_OK; + } + + req = nxt_java_get_response_info(req_info_ptr, 1, name_len + value_len + 2); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + return nxt_unit_response_add_field(req, name, name_len, value, value_len); +} + + +static void JNICALL +nxt_java_Response_setHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jarray value) +{ + int rc; + char *name_str, *value_str; + jsize name_len, value_len; + nxt_unit_request_info_t *req; + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + value_str = (*env)->GetPrimitiveArrayCritical(env, value, NULL); + if (value_str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); + + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get value content"); + + return; + } + + name_len = (*env)->GetArrayLength(env, name); + value_len = (*env)->GetArrayLength(env, value); + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, value, value_str, 0); + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name) +{ + int rc; + char *name_str; + jsize name_len; + nxt_unit_request_info_t *req; + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + req = nxt_jlong2ptr(req_info_ptr); + nxt_unit_req_warn(req, "setHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_remove_header(req_info_ptr, name_str, name_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static int +nxt_java_response_remove_header(jlong req_info_ptr, + const char *name, jint name_len) +{ + nxt_unit_field_t *f, *e; + nxt_unit_response_t *resp; + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return NXT_UNIT_ERROR; + } + + resp = req->response; + + f = resp->fields; + e = f + resp->fields_count; + + for ( ;; ) { + f = nxt_java_findHeader(f, e, name, name_len); + if (f == NULL) { + break; + } + + f->skip = 1; + + ++f; + } + + return NXT_UNIT_OK; +} + + +static void JNICALL +nxt_java_Response_setIntHeader(JNIEnv *env, jclass cls, + jlong req_info_ptr, jarray name, jint value) +{ + int value_len, rc; + char value_str[40]; + char *name_str; + jsize name_len; + + value_len = snprintf(value_str, sizeof(value_str), "%d", (int) value); + + name_len = (*env)->GetArrayLength(env, name); + + name_str = (*env)->GetPrimitiveArrayCritical(env, name, NULL); + if (name_str == NULL) { + nxt_unit_req_warn(nxt_jlong2ptr(req_info_ptr), + "setIntHeader: failed to get name content"); + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, name_str, name_len, + value_str, value_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, name, name_str, 0); +} + + +static void JNICALL +nxt_java_Response_setStatus(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint sc) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->status = sc; +} + + +static jstring JNICALL +nxt_java_Response_getContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_field_t *f; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_req_debug(req, "getContentType: response is not initialized"); + return NULL; + } + + if (nxt_unit_response_is_sent(req)) { + nxt_unit_req_debug(req, "getContentType: response already sent"); + return NULL; + } + + f = nxt_java_findHeader(req->response->fields, + req->response->fields + req->response->fields_count, + "Content-Type", sizeof("Content-Type") - 1); + + if (f == NULL) { + return NULL; + } + + return nxt_java_newString(env, nxt_unit_sptr_get(&f->value), + f->value_length); +} + + +static jboolean JNICALL +nxt_java_Response_isCommitted(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + return 1; + } + + return 0; +} + + +static void JNICALL +nxt_java_Response_reset(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_buf_t *buf; + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + + if (nxt_unit_response_is_sent(req)) { + nxt_java_throw_IllegalStateException(env, "Response already sent"); + + return; + } + + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } + + if (nxt_unit_response_is_init(req)) { + req->response->status = 200; + req->response->fields_count = 0; + + buf = req->response_buf; + + buf->free = buf->start + req->response_max_fields + * sizeof(nxt_unit_field_t); + } +} + + +static void JNICALL +nxt_java_Response_resetBuffer(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf != NULL && data->buf->free > data->buf->start) { + data->buf->free = data->buf->start; + } +} + + +static void JNICALL +nxt_java_Response_setBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr, + jint size) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + if (data->buf_size == (uint32_t) size) { + return; + } + + if (data->buf != NULL && data->buf->free > data->buf->start) { + nxt_java_throw_IllegalStateException(env, "Buffer is not empty"); + + return; + } + + data->buf_size = size; + + if (data->buf_size > nxt_unit_buf_max()) { + data->buf_size = nxt_unit_buf_max(); + } + + if (data->buf != NULL + && (uint32_t) (data->buf->end - data->buf->start) < data->buf_size) + { + nxt_unit_buf_free(data->buf); + + data->buf = NULL; + } +} + + +static jint JNICALL +nxt_java_Response_getBufferSize(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_unit_request_info_t *req; + nxt_java_request_data_t *data; + + req = nxt_jlong2ptr(req_info_ptr); + data = req->data; + + return data->buf_size; +} + + +static void JNICALL +nxt_java_Response_setContentLength(JNIEnv *env, jclass cls, jlong req_info_ptr, + jlong len) +{ + nxt_unit_request_info_t *req; + + req = nxt_java_get_response_info(req_info_ptr, 0, 0); + if (req == NULL) { + return; + } + + req->response->content_length = len; +} + + +static void JNICALL +nxt_java_Response_setContentType(JNIEnv *env, jclass cls, jlong req_info_ptr, + jarray type) +{ + int rc; + char *type_str; + jsize type_len; + + static const char content_type[] = "Content-Type"; + static const uint32_t content_type_len = sizeof(content_type) - 1; + + type_len = (*env)->GetArrayLength(env, type); + + type_str = (*env)->GetPrimitiveArrayCritical(env, type, NULL); + if (type_str == NULL) { + return; + } + + rc = nxt_java_response_set_header(req_info_ptr, + content_type, content_type_len, + type_str, type_len); + if (rc != NXT_UNIT_OK) { + // throw + } + + (*env)->ReleasePrimitiveArrayCritical(env, type, type_str, 0); +} + + +static void JNICALL +nxt_java_Response_removeContentType(JNIEnv *env, jclass cls, jlong req_info_ptr) +{ + nxt_java_response_remove_header(req_info_ptr, "Content-Type", + sizeof("Content-Type") - 1); +} + + +static void JNICALL +nxt_java_Response_log(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "log: failed to get msg content"); + return; + } + + nxt_unit_req_log(req, NXT_UNIT_LOG_INFO, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +} + + +static void JNICALL +nxt_java_Response_trace(JNIEnv *env, jclass cls, jlong req_info_ptr, jarray msg) +{ +#if (NXT_DEBUG) + char *msg_str; + jsize msg_len; + nxt_unit_request_info_t *req; + + req = nxt_jlong2ptr(req_info_ptr); + msg_len = (*env)->GetArrayLength(env, msg); + + msg_str = (*env)->GetPrimitiveArrayCritical(env, msg, NULL); + if (msg_str == NULL) { + nxt_unit_req_warn(req, "trace: failed to get msg content"); + return; + } + + nxt_unit_req_debug(req, "%.*s", msg_len, msg_str); + + (*env)->ReleasePrimitiveArrayCritical(env, msg, msg_str, 0); +#endif +} + diff --git a/src/java/nxt_jni_Response.h b/src/java/nxt_jni_Response.h new file mode 100644 index 00000000..d10dba58 --- /dev/null +++ b/src/java/nxt_jni_Response.h @@ -0,0 +1,18 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_RESPONSE_H_INCLUDED_ +#define _NXT_JAVA_RESPONSE_H_INCLUDED_ + + +#include <jni.h> +#include <nxt_unit_typedefs.h> + + +int nxt_java_initResponse(JNIEnv *env, jobject cl); + +jobject nxt_java_newResponse(JNIEnv *env, nxt_unit_request_info_t *req); + +#endif /* _NXT_JAVA_RESPONSE_H_INCLUDED_ */ diff --git a/src/java/nxt_jni_Thread.c b/src/java/nxt_jni_Thread.c new file mode 100644 index 00000000..43dd90bd --- /dev/null +++ b/src/java/nxt_jni_Thread.c @@ -0,0 +1,94 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_unit.h> +#include <jni.h> + +#include "nxt_jni_Thread.h" + + +static jclass nxt_java_Thread_class; +static jmethodID nxt_java_Thread_currentThread; +static jmethodID nxt_java_Thread_getContextClassLoader; +static jmethodID nxt_java_Thread_setContextClassLoader; + + +int +nxt_java_initThread(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/lang/Thread"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_Thread_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_Thread_class; + + nxt_java_Thread_currentThread = (*env)->GetStaticMethodID(env, cls, + "currentThread", "()Ljava/lang/Thread;"); + if (nxt_java_Thread_currentThread == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.currentThread() not found"); + goto failed; + } + + nxt_java_Thread_getContextClassLoader = (*env)->GetMethodID(env, cls, + "getContextClassLoader", "()Ljava/lang/ClassLoader;"); + if (nxt_java_Thread_getContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.getContextClassLoader() " + "not found"); + goto failed; + } + + nxt_java_Thread_setContextClassLoader = (*env)->GetMethodID(env, cls, + "setContextClassLoader", "(Ljava/lang/ClassLoader;)V"); + if (nxt_java_Thread_setContextClassLoader == NULL) { + nxt_unit_warn(NULL, "java.lang.Thread.setContextClassLoader() " + "not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + +void +nxt_java_setContextClassLoader(JNIEnv *env, jobject cl) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return; + } + + (*env)->CallVoidMethod(env, thread, nxt_java_Thread_setContextClassLoader, + cl); +} + +jobject +nxt_java_getContextClassLoader(JNIEnv *env) +{ + jobject thread; + + thread = (*env)->CallStaticObjectMethod(env, nxt_java_Thread_class, + nxt_java_Thread_currentThread); + + if (thread == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, thread, + nxt_java_Thread_getContextClassLoader); +} diff --git a/src/java/nxt_jni_Thread.h b/src/java/nxt_jni_Thread.h new file mode 100644 index 00000000..4d0b650e --- /dev/null +++ b/src/java/nxt_jni_Thread.h @@ -0,0 +1,20 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_THREAD_H_INCLUDED_ +#define _NXT_JAVA_THREAD_H_INCLUDED_ + + +#include <jni.h> + + +int nxt_java_initThread(JNIEnv *env); + +void nxt_java_setContextClassLoader(JNIEnv *env, jobject cl); + +jobject nxt_java_getContextClassLoader(JNIEnv *env); + +#endif /* _NXT_JAVA_THREAD_H_INCLUDED_ */ + diff --git a/src/java/nxt_jni_URLClassLoader.c b/src/java/nxt_jni_URLClassLoader.c new file mode 100644 index 00000000..bf3ab0c3 --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.c @@ -0,0 +1,187 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_unit.h> +#include <jni.h> + +#include "nxt_jni_URLClassLoader.h" + + +static jclass nxt_java_URLClassLoader_class; +static jmethodID nxt_java_URLClassLoader_ctor; +static jmethodID nxt_java_URLClassLoader_parent_ctor; +static jmethodID nxt_java_URLClassLoader_loadClass; +static jmethodID nxt_java_URLClassLoader_addURL; + +static jclass nxt_java_URL_class; +static jmethodID nxt_java_URL_ctor; + + +int +nxt_java_initURLClassLoader(JNIEnv *env) +{ + jclass cls; + + cls = (*env)->FindClass(env, "java/net/URLClassLoader"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URLClassLoader_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URLClassLoader_class; + + nxt_java_URLClassLoader_ctor = (*env)->GetMethodID(env, cls, + "<init>", "([Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_parent_ctor = (*env)->GetMethodID(env, cls, + "<init>", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V"); + if (nxt_java_URLClassLoader_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader constructor not found"); + goto failed; + } + + nxt_java_URLClassLoader_loadClass = (*env)->GetMethodID(env, cls, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + if (nxt_java_URLClassLoader_loadClass == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.loadClass not found"); + goto failed; + } + + nxt_java_URLClassLoader_addURL = (*env)->GetMethodID(env, cls, + "addURL", "(Ljava/net/URL;)V"); + if (nxt_java_URLClassLoader_addURL == NULL) { + nxt_unit_warn(NULL, "java.net.URLClassLoader.addURL not found"); + goto failed; + } + + cls = (*env)->FindClass(env, "java/net/URL"); + if (cls == NULL) { + nxt_unit_warn(NULL, "java.net.URL not found"); + return NXT_UNIT_ERROR; + } + + nxt_java_URL_class = (*env)->NewGlobalRef(env, cls); + (*env)->DeleteLocalRef(env, cls); + cls = nxt_java_URL_class; + + nxt_java_URL_ctor = (*env)->GetMethodID(env, cls, + "<init>", "(Ljava/lang/String;)V"); + if (nxt_java_URL_ctor == NULL) { + nxt_unit_warn(NULL, "java.net.URL constructor not found"); + goto failed; + } + + return NXT_UNIT_OK; + +failed: + + (*env)->DeleteGlobalRef(env, cls); + return NXT_UNIT_ERROR; +} + + +jobject +nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_ctor, jurls); +} + + +jobject +nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, char **urls, + jobject parent) +{ + jobjectArray jurls; + + jurls = nxt_java_newURLs(env, url_count, urls); + if (jurls == NULL) { + return NULL; + } + + return (*env)->NewObject(env, nxt_java_URLClassLoader_class, + nxt_java_URLClassLoader_parent_ctor, jurls, + parent); +} + + +jobjectArray +nxt_java_newURLs(JNIEnv *env, int url_count, char **urls) +{ + int i; + jstring surl; + jobject jurl; + jobjectArray jurls; + + jurls = (*env)->NewObjectArray(env, url_count, nxt_java_URL_class, NULL); + if (jurls == NULL) { + return NULL; + } + + for (i = 0; i < url_count; i++) { + surl = (*env)->NewStringUTF(env, urls[i]); + if (surl == NULL) { + return NULL; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, + surl); + if (jurl == NULL) { + return NULL; + } + + (*env)->SetObjectArrayElement(env, jurls, i, jurl); + } + + return jurls; +} + + +jclass +nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name) +{ + jstring jname; + + jname = (*env)->NewStringUTF(env, name); + if (jname == NULL) { + return NULL; + } + + return (*env)->CallObjectMethod(env, cl, nxt_java_URLClassLoader_loadClass, + jname); +} + + +void +nxt_java_addURL(JNIEnv *env, jobject cl, const char *url) +{ + jstring surl; + jobject jurl; + + surl = (*env)->NewStringUTF(env, url); + if (surl == NULL) { + return; + } + + jurl = (*env)->NewObject(env, nxt_java_URL_class, nxt_java_URL_ctor, surl); + if (jurl == NULL) { + return; + } + + (*env)->CallVoidMethod(env, cl, nxt_java_URLClassLoader_addURL, jurl); +} diff --git a/src/java/nxt_jni_URLClassLoader.h b/src/java/nxt_jni_URLClassLoader.h new file mode 100644 index 00000000..4cf2c0ec --- /dev/null +++ b/src/java/nxt_jni_URLClassLoader.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ +#define _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ + + +#include <jni.h> + + +int nxt_java_initURLClassLoader(JNIEnv *env); + +jobject nxt_java_newURLClassLoader(JNIEnv *env, int url_count, char **urls); + +jobject nxt_java_newURLClassLoader_parent(JNIEnv *env, int url_count, + char **urls, jobject parent); + +jobjectArray nxt_java_newURLs(JNIEnv *env, int url_count, char **urls); + +jclass nxt_java_loadClass(JNIEnv *env, jobject cl, const char *name); + +void nxt_java_addURL(JNIEnv *env, jobject cl, const char *url); + +#endif /* _NXT_JAVA_URLCLASSLOADER_H_INCLUDED_ */ + diff --git a/src/nxt_application.c b/src/nxt_application.c index acdebe04..a2827b75 100644 --- a/src/nxt_application.c +++ b/src/nxt_application.c @@ -331,6 +331,17 @@ nxt_app_start(nxt_task_t *task, void *data) nxt_app = nxt_app_module_load(task, lang->file); } + if (nxt_app->pre_init != NULL) { + ret = nxt_app->pre_init(task, data); + + if (nxt_slow_path(ret != NXT_OK)) { + nxt_debug(task, "application pre_init failed"); + + } else { + nxt_debug(task, "application pre_init done"); + } + } + if (app_conf->working_directory != NULL && app_conf->working_directory[0] != 0) { @@ -521,6 +532,9 @@ nxt_app_parse_type(u_char *p, size_t length) } else if (nxt_str_eq(&str, "ruby", 4)) { return NXT_APP_RUBY; + + } else if (nxt_str_eq(&str, "java", 4)) { + return NXT_APP_JAVA; } return NXT_APP_UNKNOWN; diff --git a/src/nxt_application.h b/src/nxt_application.h index fe8113c6..781f05e0 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -20,6 +20,7 @@ typedef enum { NXT_APP_PHP, NXT_APP_PERL, NXT_APP_RUBY, + NXT_APP_JAVA, NXT_APP_UNKNOWN, } nxt_app_type_t; @@ -70,6 +71,14 @@ typedef struct { } nxt_ruby_app_conf_t; +typedef struct { + nxt_conf_value_t *classpath; + char *webapp; + nxt_conf_value_t *options; + char *unit_jars; +} nxt_java_app_conf_t; + + struct nxt_common_app_conf_s { nxt_str_t name; nxt_str_t type; @@ -85,6 +94,7 @@ struct nxt_common_app_conf_s { nxt_php_app_conf_t php; nxt_perl_app_conf_t perl; nxt_ruby_app_conf_t ruby; + nxt_java_app_conf_t java; } u; }; @@ -152,6 +162,8 @@ struct nxt_app_module_s { nxt_str_t type; const char *version; + nxt_int_t (*pre_init)(nxt_task_t *task, + nxt_common_app_conf_t *conf); nxt_int_t (*init)(nxt_task_t *task, nxt_common_app_conf_t *conf); }; diff --git a/src/nxt_conf.h b/src/nxt_conf.h index 052334fe..20ff3b1e 100644 --- a/src/nxt_conf.h +++ b/src/nxt_conf.h @@ -125,7 +125,7 @@ void nxt_conf_set_element(nxt_conf_value_t *array, nxt_uint_t index, nxt_conf_value_t *value); nxt_int_t nxt_conf_set_element_string_dup(nxt_conf_value_t *array, nxt_mp_t *mp, nxt_uint_t index, nxt_str_t *value); -nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); +NXT_EXPORT nxt_uint_t nxt_conf_array_elements_count(nxt_conf_value_t *value); void nxt_conf_array_qsort(nxt_conf_value_t *value, int (*compare)(const void *, const void *)); diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 655097fd..5653b9eb 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -88,6 +88,10 @@ static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value); static nxt_conf_vldt_object_t nxt_conf_vldt_http_members[] = { @@ -423,6 +427,31 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_ruby_members[] = { }; +static nxt_conf_vldt_object_t nxt_conf_vldt_java_members[] = { + { nxt_string("classpath"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_classpath}, + + { nxt_string("webapp"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + { nxt_string("options"), + NXT_CONF_VLDT_ARRAY, + &nxt_conf_vldt_array_iterator, + (void *) &nxt_conf_vldt_java_option}, + + { nxt_string("unit_jars"), + NXT_CONF_VLDT_STRING, + NULL, + NULL }, + + NXT_CONF_VLDT_NEXT(&nxt_conf_vldt_common_members) +}; + + nxt_int_t nxt_conf_validate(nxt_conf_validation_t *vldt) { @@ -818,6 +847,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_vldt_php_members, nxt_conf_vldt_perl_members, nxt_conf_vldt_ruby_members, + nxt_conf_vldt_java_members, }; ret = nxt_conf_vldt_type(vldt, name, value, NXT_CONF_VLDT_OBJECT); @@ -1216,3 +1246,44 @@ nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, return NXT_OK; } + + +static nxt_int_t +nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"classpath\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} + +static nxt_int_t +nxt_conf_vldt_java_option(nxt_conf_validation_t *vldt, nxt_conf_value_t *value) +{ + nxt_str_t str; + + if (nxt_conf_type(value) != NXT_CONF_STRING) { + return nxt_conf_vldt_error(vldt, "The \"options\" array " + "must contain only string values."); + } + + nxt_conf_get_string(value, &str); + + if (nxt_memchr(str.start, '\0', str.length) != NULL) { + return nxt_conf_vldt_error(vldt, "The \"options\" array must not " + "contain strings with null character."); + } + + return NXT_OK; +} diff --git a/src/nxt_external.c b/src/nxt_external.c index c7aacffc..89fe08c8 100644 --- a/src/nxt_external.c +++ b/src/nxt_external.c @@ -18,6 +18,7 @@ nxt_app_module_t nxt_external_module = { NULL, nxt_string("external"), "*", + NULL, nxt_external_init, }; diff --git a/src/nxt_java.c b/src/nxt_java.c new file mode 100644 index 00000000..bf4931ab --- /dev/null +++ b/src/nxt_java.c @@ -0,0 +1,462 @@ + +/* + * Copyright (C) NGINX, Inc. + */ + + +#include <jni.h> + +#include <nxt_main.h> +#include <nxt_runtime.h> +#include <nxt_router.h> +#include <nxt_unit.h> +#include <nxt_unit_field.h> +#include <nxt_unit_request.h> +#include <nxt_unit_response.h> + +#include <java/nxt_jni.h> + +#include "java/nxt_jni_Thread.h" +#include "java/nxt_jni_Context.h" +#include "java/nxt_jni_Request.h" +#include "java/nxt_jni_Response.h" +#include "java/nxt_jni_InputStream.h" +#include "java/nxt_jni_OutputStream.h" +#include "java/nxt_jni_URLClassLoader.h" + +#include "nxt_jars.h" + +static nxt_int_t nxt_java_pre_init(nxt_task_t *task, + nxt_common_app_conf_t *conf); +static nxt_int_t nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf); +static void nxt_java_request_handler(nxt_unit_request_info_t *req); + +static uint32_t compat[] = { + NXT_VERNUM, NXT_DEBUG, +}; + +char *nxt_java_modules; + + +#define NXT_STRING(x) _NXT_STRING(x) +#define _NXT_STRING(x) #x + +NXT_EXPORT nxt_app_module_t nxt_app_module = { + sizeof(compat), + compat, + nxt_string("java"), + NXT_STRING(NXT_JAVA_VERSION), + nxt_java_pre_init, + nxt_java_init, +}; + +typedef struct { + JNIEnv *env; + jobject ctx; +} nxt_java_data_t; + + +static nxt_int_t +nxt_java_pre_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + const char *unit_jars; + + unit_jars = conf->u.java.unit_jars; + if (unit_jars == NULL) { + unit_jars = NXT_JARS; + } + + nxt_java_modules = realpath(unit_jars, NULL); + if (nxt_java_modules == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", NXT_JARS, nxt_errno); + return NXT_ERROR; + } + + return NXT_OK; +} + + +static char ** +nxt_java_module_jars(const char *jars[], int jar_count) +{ + char **res, *jurl; + nxt_int_t modules_len, jlen, i; + const char **jar; + + res = nxt_malloc(jar_count * sizeof(char*)); + if (res == NULL) { + return NULL; + } + + modules_len = nxt_strlen(nxt_java_modules); + + for (i = 0, jar = jars; *jar != NULL; jar++) { + jlen = nxt_length("file:") + modules_len + nxt_length("/") + + nxt_strlen(*jar) + 1; + jurl = nxt_malloc(jlen); + if (jurl == NULL) { + return NULL; + } + + res[i++] = jurl; + + jurl = nxt_cpymem(jurl, "file:", nxt_length("file:")); + jurl = nxt_cpymem(jurl, nxt_java_modules, modules_len); + *jurl++ = '/'; + jurl = nxt_cpymem(jurl, *jar, nxt_strlen(*jar)); + *jurl++ = '\0'; + } + + return res; +} + + +static nxt_int_t +nxt_java_init(nxt_task_t *task, nxt_common_app_conf_t *conf) +{ + jint rc; + char *opt, *real_path; + char **classpath_arr, **unit_jars, **system_jars; + JavaVM *jvm; + JNIEnv *env; + jobject cl, classpath; + nxt_str_t str; + nxt_int_t opt_len, real_path_len; + nxt_uint_t i, unit_jars_count, classpath_count, system_jars_count; + JavaVMOption *jvm_opt; + JavaVMInitArgs jvm_args; + nxt_unit_ctx_t *ctx; + nxt_unit_init_t java_init; + nxt_java_data_t data; + nxt_conf_value_t *value; + nxt_java_app_conf_t *c; + + //setenv("ASAN_OPTIONS", "handle_segv=0", 1); + + jvm_args.version = JNI_VERSION_1_6; + jvm_args.nOptions = 0; + jvm_args.ignoreUnrecognized = 0; + + c = &conf->u.java; + + if (c->options != NULL) { + jvm_args.nOptions += nxt_conf_array_elements_count(c->options); + } + + jvm_opt = nxt_malloc(jvm_args.nOptions * sizeof(JavaVMOption)); + if (jvm_opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + jvm_args.options = jvm_opt; + + unit_jars_count = nxt_nitems(nxt_java_unit_jars) - 1; + + unit_jars = nxt_java_module_jars(nxt_java_unit_jars, unit_jars_count); + if (unit_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for unit_jars array"); + + return NXT_ERROR; + } + + system_jars_count = nxt_nitems(nxt_java_system_jars) - 1; + + system_jars = nxt_java_module_jars(nxt_java_system_jars, system_jars_count); + if (system_jars == NULL) { + nxt_alert(task, "failed to allocate buffer for system_jars array"); + + return NXT_ERROR; + } + + if (c->options != NULL) { + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->options, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt = nxt_malloc(str.length + 1); + if (opt == NULL) { + nxt_alert(task, "failed to allocate jvm_opt"); + return NXT_ERROR; + } + + memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + jvm_opt[i].optionString = opt; + } + } + + if (c->classpath != NULL) { + classpath_count = nxt_conf_array_elements_count(c->classpath); + classpath_arr = nxt_malloc(classpath_count * sizeof(char *)); + + for (i = 0; /* void */ ; i++) { + value = nxt_conf_get_array_element(c->classpath, i); + if (value == NULL) { + break; + } + + nxt_conf_get_string(value, &str); + + opt_len = str.length + 1; + + char *sc = memchr(str.start, ':', str.length); + if (sc == NULL && str.start[0] == '/') { + opt_len += nxt_length("file:"); + } + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + if (sc == NULL && str.start[0] != '/') { + nxt_memcpy(opt, str.start, str.length); + opt[str.length] = '\0'; + + real_path = realpath(opt, NULL); + if (real_path == NULL) { + nxt_alert(task, "realpath(%s) failed: %E", opt, nxt_errno); + return NXT_ERROR; + } + + real_path_len = nxt_strlen(real_path); + + free(opt); + + opt_len = nxt_length("file:") + real_path_len + 1; + + opt = nxt_malloc(opt_len); + if (opt == NULL) { + nxt_alert(task, "failed to allocate classpath"); + return NXT_ERROR; + } + + } else { + real_path = (char *) str.start; /* I love this cast! */ + real_path_len = str.length; + } + + classpath_arr[i] = opt; + + if (sc == NULL) { + opt = nxt_cpymem(opt, "file:", nxt_length("file:")); + } + + opt = nxt_cpymem(opt, real_path, real_path_len); + *opt = '\0'; + } + + } else { + classpath_count = 0; + classpath_arr = NULL; + } + + rc = JNI_CreateJavaVM(&jvm, (void **) &env, &jvm_args); + if (rc != JNI_OK) { + nxt_alert(task, "failed to create Java VM: %d", (int) rc); + return NXT_ERROR; + } + + rc = nxt_java_initThread(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initThread() failed"); + goto env_failed; + } + + rc = nxt_java_initURLClassLoader(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initURLClassLoader() failed"); + goto env_failed; + } + + cl = nxt_java_newURLClassLoader(env, system_jars_count, system_jars); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + cl = nxt_java_newURLClassLoader_parent(env, unit_jars_count, unit_jars, cl); + if (cl == NULL) { + nxt_alert(task, "nxt_java_newURLClassLoader_parent failed"); + goto env_failed; + } + + nxt_java_setContextClassLoader(env, cl); + + rc = nxt_java_initContext(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initContext() failed"); + goto env_failed; + } + + rc = nxt_java_initRequest(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initRequest() failed"); + goto env_failed; + } + + rc = nxt_java_initResponse(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initResponse() failed"); + goto env_failed; + } + + rc = nxt_java_initInputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initInputStream() failed"); + goto env_failed; + } + + rc = nxt_java_initOutputStream(env, cl); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_initOutputStream() failed"); + goto env_failed; + } + + nxt_java_jni_init(env); + if (rc != NXT_UNIT_OK) { + nxt_alert(task, "nxt_java_jni_init() failed"); + goto env_failed; + } + + classpath = nxt_java_newURLs(env, classpath_count, classpath_arr); + if (classpath == NULL) { + nxt_alert(task, "nxt_java_newURLs failed"); + goto env_failed; + } + + data.env = env; + data.ctx = nxt_java_startContext(env, c->webapp, classpath); + + if ((*env)->ExceptionCheck(env)) { + nxt_alert(task, "Unhandled exception in application start"); + (*env)->ExceptionDescribe(env); + return NXT_ERROR; + } + + nxt_unit_default_init(task, &java_init); + + java_init.callbacks.request_handler = nxt_java_request_handler; + java_init.request_data_size = sizeof(nxt_java_request_data_t); + java_init.data = &data; + + ctx = nxt_unit_init(&java_init); + if (nxt_slow_path(ctx == NULL)) { + nxt_alert(task, "nxt_unit_init() failed"); + return NXT_ERROR; + } + + rc = nxt_unit_run(ctx); + if (nxt_slow_path(rc != NXT_UNIT_OK)) { + /* TODO report error */ + } + + nxt_unit_done(ctx); + + nxt_java_stopContext(env, data.ctx); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + (*jvm)->DestroyJavaVM(jvm); + + exit(0); + + return NXT_OK; + +env_failed: + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + } + + return NXT_ERROR; +} + + +static void +nxt_java_request_handler(nxt_unit_request_info_t *req) +{ + JNIEnv *env; + jobject jreq, jresp; + nxt_java_data_t *java_data; + nxt_java_request_data_t *data; + + java_data = req->unit->data; + env = java_data->env; + data = req->data; + + jreq = nxt_java_newRequest(env, java_data->ctx, req); + if (jreq == NULL) { + nxt_unit_req_alert(req, "failed to create Request instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + jresp = nxt_java_newResponse(env, req); + if (jresp == NULL) { + nxt_unit_req_alert(req, "failed to create Response instance"); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_ERROR); + return; + } + + data->header_size = 10 * 1024; + data->buf_size = 32 * 1024; /* from Jetty */ + data->jreq = jreq; + data->jresp = jresp; + data->buf = NULL; + + nxt_unit_request_group_dup_fields(req); + + nxt_java_service(env, java_data->ctx, jreq, jresp); + + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + } + + if (!nxt_unit_response_is_init(req)) { + nxt_unit_response_init(req, 200, 0, 0); + } + + if (!nxt_unit_response_is_sent(req)) { + nxt_unit_response_send(req); + } + + if (data->buf != NULL) { + nxt_unit_buf_send(data->buf); + + data->buf = NULL; + } + + (*env)->DeleteLocalRef(env, jresp); + (*env)->DeleteLocalRef(env, jreq); + + nxt_unit_request_done(req, NXT_UNIT_OK); +} + diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index a33117e2..f756bff7 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -219,12 +219,38 @@ static nxt_conf_map_t nxt_ruby_app_conf[] = { }; +static nxt_conf_map_t nxt_java_app_conf[] = { + { + nxt_string("classpath"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.classpath), + }, + { + nxt_string("webapp"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.webapp), + }, + { + nxt_string("options"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.java.options), + }, + { + nxt_string("unit_jars"), + NXT_CONF_MAP_CSTRZ, + offsetof(nxt_common_app_conf_t, u.java.unit_jars), + }, + +}; + + static nxt_conf_app_map_t nxt_app_maps[] = { { nxt_nitems(nxt_external_app_conf), nxt_external_app_conf }, { nxt_nitems(nxt_python_app_conf), nxt_python_app_conf }, { nxt_nitems(nxt_php_app_conf), nxt_php_app_conf }, { nxt_nitems(nxt_perl_app_conf), nxt_perl_app_conf }, { nxt_nitems(nxt_ruby_app_conf), nxt_ruby_app_conf }, + { nxt_nitems(nxt_java_app_conf), nxt_java_app_conf }, }; diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 559a3e37..e36dd20a 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -164,6 +164,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("php"), PHP_VERSION, + NULL, nxt_php_init, }; diff --git a/src/nxt_python_wsgi.c b/src/nxt_python_wsgi.c index 871c8e91..80ffb0f7 100644 --- a/src/nxt_python_wsgi.c +++ b/src/nxt_python_wsgi.c @@ -105,6 +105,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("python"), PY_VERSION, + NULL, nxt_python_init, }; diff --git a/src/nxt_router.c b/src/nxt_router.c index 51c84df9..e46e8f82 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -293,6 +293,7 @@ static const nxt_str_t *nxt_app_msg_prefix[] = { &http_prefix, &http_prefix, &http_prefix, + &empty_prefix, }; diff --git a/src/perl/nxt_perl_psgi.c b/src/perl/nxt_perl_psgi.c index 275ed913..efcff7e3 100644 --- a/src/perl/nxt_perl_psgi.c +++ b/src/perl/nxt_perl_psgi.c @@ -113,6 +113,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { nxt_perl_psgi_compat, nxt_string("perl"), PERL_VERSION_STRING, + NULL, nxt_perl_psgi_init, }; diff --git a/src/ruby/nxt_ruby.c b/src/ruby/nxt_ruby.c index d099a338..17831175 100644 --- a/src/ruby/nxt_ruby.c +++ b/src/ruby/nxt_ruby.c @@ -76,6 +76,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { compat, nxt_string("ruby"), ruby_version, + NULL, nxt_ruby_init, }; |