diff --git a/pom.xml b/pom.xml index 0204b53e09008a131aedcc837c47c89e681ae1d8..8a7ee622900f770cdce4d84dd37bb7e2a55cd471 100644 --- a/pom.xml +++ b/pom.xml @@ -9,10 +9,10 @@ pom - 1.2.0 + 1.2.1 0.2.1 - 3.1.0 + 4.0.1 1.3.2 UTF-8 UTF-8 @@ -43,6 +43,11 @@ javax.servlet-api ${servlet.version} + + jakarta.servlet + jakarta.servlet-api + 5.0.0 + javax.annotation javax.annotation-api @@ -229,11 +234,11 @@ - - alimaven - aliyun maven - http://maven.aliyun.com/nexus/content/groups/public/ - + + + + + sonatype-nexus-snapshots Sonatype Nexus Snapshots diff --git a/servlet-core/pom.xml b/servlet-core/pom.xml index c0200935790e465a4f2737507e38836d81d53e3a..015a97230c3750e3e69cd8801ba617b82552da3b 100644 --- a/servlet-core/pom.xml +++ b/servlet-core/pom.xml @@ -29,6 +29,10 @@ javax.servlet javax.servlet-api + + jakarta.servlet + jakarta.servlet-api + javax.annotation javax.annotation-api diff --git a/servlet-core/src/main/java/org/smartboot/servlet/HandlesTypesLoader.java b/servlet-core/src/main/java/org/smartboot/servlet/AnnotationsLoader.java similarity index 81% rename from servlet-core/src/main/java/org/smartboot/servlet/HandlesTypesLoader.java rename to servlet-core/src/main/java/org/smartboot/servlet/AnnotationsLoader.java index cdeb53b8dab54c2d69a022b99427ae1776f2f442..d7e6fd5b506fd5379063fd6c2124313c9b8f958e 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/HandlesTypesLoader.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/AnnotationsLoader.java @@ -10,13 +10,19 @@ package org.smartboot.servlet; +import org.smartboot.http.common.utils.StringUtils; +import org.smartboot.servlet.conf.ServletInfo; import org.smartboot.servlet.third.bcel.Const; import org.smartboot.servlet.third.bcel.classfile.AnnotationEntry; import org.smartboot.servlet.third.bcel.classfile.ClassParser; import org.smartboot.servlet.third.bcel.classfile.JavaClass; +import org.smartboot.servlet.util.CollectionUtils; import javax.servlet.ServletContainerInitializer; import javax.servlet.annotation.HandlesTypes; +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebListener; +import javax.servlet.annotation.WebServlet; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -24,11 +30,13 @@ import java.io.InputStream; import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; +import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CountDownLatch; @@ -41,7 +49,7 @@ import java.util.jar.JarFile; * @author 三刀(zhengjunweimail@163.com) * @version V1.0 , 2021/6/27 */ -public class HandlesTypesLoader { +public class AnnotationsLoader { private final Map>> initializerClassMap = new LinkedHashMap<>(); /** @@ -62,7 +70,11 @@ public class HandlesTypesLoader { */ private boolean handlesTypesNonAnnotations = false; - public HandlesTypesLoader(ClassLoader classLoader) { + private Map> annotations = new HashMap<>(); + + private final Map servlets = new HashMap<>(); + + public AnnotationsLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @@ -82,12 +94,9 @@ public class HandlesTypesLoader { private ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); - public Map>> scanHandleTypes() { - if (typeInitializerMap.isEmpty()) { - return Collections.emptyMap(); - } + public void scanAnnotations() { if (!(classLoader instanceof URLClassLoader)) { - return Collections.emptyMap(); + return; } Map javaClassCache = new HashMap<>(); URL[] urls = ((URLClassLoader) classLoader).getURLs(); @@ -119,9 +128,21 @@ public class HandlesTypesLoader { throw new RuntimeException(e); } javaClassCache.clear(); + } + + public Map>> getInitializerClassMap() { return initializerClassMap; } + public Map getServlets() { + return servlets; + } + + public List getAnnotations(Class clazz) { + List classes = annotations.get(clazz); + return CollectionUtils.isEmpty(classes) ? Collections.emptyList() : classes; + } + private void processAnnotationsJar(URL url, Map javaClassCache) { try { JarFile jarFile = new JarFile(url.getFile()); @@ -137,7 +158,7 @@ public class HandlesTypesLoader { try { ClassParser parser = new ClassParser(jarFile.getInputStream(jarEntry)); JavaClass clazz = parser.parse(); - checkHandlesTypes(clazz, javaClassCache); + checkAnnotation(clazz, javaClassCache); } catch (IOException e) { e.printStackTrace(); } @@ -163,7 +184,7 @@ public class HandlesTypesLoader { try (FileInputStream fis = new FileInputStream(file)) { ClassParser parser = new ClassParser(fis); JavaClass clazz = parser.parse(); - checkHandlesTypes(clazz, javaClassCache); + checkAnnotation(clazz, javaClassCache); } catch (Exception e) { e.printStackTrace(); } @@ -172,14 +193,11 @@ public class HandlesTypesLoader { } } - private void checkHandlesTypes(JavaClass javaClass, - Map javaClassCache) { - - // Skip this if we can - if (typeInitializerMap.size() == 0) { - return; - } - + /** + * 检查是否包含待加载的注解 + */ + private void checkAnnotation(JavaClass javaClass, + Map javaClassCache) throws ClassNotFoundException { if ((javaClass.getAccessFlags() & Const.ACC_ANNOTATION) != 0) { // Skip annotations. return; @@ -187,6 +205,38 @@ public class HandlesTypesLoader { String className = javaClass.getClassName(); + if (javaClass.getAnnotationEntries() != null) { + for (AnnotationEntry entry : javaClass.getAnnotationEntries()) { + System.out.println(entry.getAnnotationType() + " " + entry.getElementValuePairs()); + String annotationName = getClassName(entry.getAnnotationType()); + if (WebListener.class.getName().equals(annotationName)) { + annotations.computeIfAbsent(WebListener.class, aClass -> new ArrayList<>()).add(className); + } else if (WebServlet.class.getName().equals(annotationName)) { + Class clazz = classLoader.loadClass(className); + WebServlet webServlet = clazz.getAnnotation(WebServlet.class); + String name = webServlet.name(); + if (StringUtils.isBlank(name)) { + name = className; + } + ServletInfo servletInfo = new ServletInfo(); + servletInfo.setServletName(name); + servletInfo.setLoadOnStartup(webServlet.loadOnStartup()); + servletInfo.setServletClass(className); + servletInfo.setAsyncSupported(webServlet.asyncSupported()); + for (WebInitParam param : webServlet.initParams()) { + servletInfo.addInitParam(param.name(), param.value()); + } + for (String urlPattern : webServlet.urlPatterns()) { + servletInfo.addMapping(urlPattern); + } + for (String url : webServlet.value()) { + servletInfo.addMapping(url); + } + servlets.put(name, servletInfo); + } + } + } + Class clazz = null; if (handlesTypesNonAnnotations) { // 从 classPath 扫描并加载 class diff --git a/servlet-core/src/main/java/org/smartboot/servlet/ContainerRuntime.java b/servlet-core/src/main/java/org/smartboot/servlet/ContainerRuntime.java index 21a2c2d052a8c3b2e6fc018a3c9eb08b450221f3..05ceeec4f63271020df7aefe5c5776c5ca1d50f7 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/ContainerRuntime.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/ContainerRuntime.java @@ -10,6 +10,9 @@ package org.smartboot.servlet; +import javax.servlet.DispatcherType; +import javax.servlet.ServletContainerInitializer; +import javax.servlet.http.HttpServletResponse; import org.smartboot.http.common.logging.Logger; import org.smartboot.http.common.logging.LoggerFactory; import org.smartboot.http.common.utils.StringUtils; @@ -33,9 +36,6 @@ import org.smartboot.servlet.impl.HttpServletResponseImpl; import org.smartboot.servlet.impl.ServletContextImpl; import org.smartboot.servlet.plugins.Plugin; -import javax.servlet.DispatcherType; -import javax.servlet.ServletContainerInitializer; -import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -46,6 +46,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; +import java.util.Enumeration; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.CopyOnWriteArrayList; @@ -306,6 +307,14 @@ public class ContainerRuntime { URLClassLoader urlClassLoader = getClassLoader(localPath, parentClassLoader); + + //加载web-fragment.xml + Enumeration fragments = urlClassLoader.getResources("META-INF/web-fragment.xml"); + while (fragments.hasMoreElements()) { + try (InputStream inputStream = fragments.nextElement().openStream()) { + engine.load(webAppInfo, inputStream); + } + } //new runtime object ServletContextRuntime servletRuntime = new ServletContextRuntime(localPath, urlClassLoader, StringUtils.isBlank(contextPath) ? "/" + contextFile.getName() : contextPath); servletRuntime.setDisplayName(webAppInfo.getDisplayName()); @@ -329,7 +338,7 @@ public class ContainerRuntime { deploymentInfo.setContextUrl(contextFile.toURI().toURL()); - deploymentInfo.setHandlesTypesLoader(new HandlesTypesLoader(deploymentInfo.getClassLoader())); + deploymentInfo.setHandlesTypesLoader(new AnnotationsLoader(deploymentInfo.getClassLoader())); for (ServletContainerInitializer containerInitializer : ServiceLoader.load(ServletContainerInitializer.class, deploymentInfo.getClassLoader())) { LOGGER.info("load ServletContainerInitializer:" + containerInitializer.getClass().getName()); deploymentInfo.addServletContainerInitializer(containerInitializer); diff --git a/servlet-core/src/main/java/org/smartboot/servlet/ServletContextRuntime.java b/servlet-core/src/main/java/org/smartboot/servlet/ServletContextRuntime.java index 7f69b10f8f7debbc824583204e1f528cc3c44fe0..79e5531e184ae7d627d3d312e08baa2684c0553d 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/ServletContextRuntime.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/ServletContextRuntime.java @@ -32,6 +32,7 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletException; +import javax.servlet.annotation.WebListener; import java.io.File; import java.util.ArrayList; import java.util.Collections; @@ -185,11 +186,23 @@ public class ServletContextRuntime { //扫描 handleType if (deploymentInfo.getHandlesTypesLoader() != null) { long start = System.currentTimeMillis(); - deploymentInfo.getHandlesTypesLoader().scanHandleTypes() - .forEach((servletContainerInitializer, handlesTypes) -> { - ServletContainerInitializerInfo initializerInfo = new ServletContainerInitializerInfo(servletContainerInitializer, handlesTypes); - deploymentInfo.getServletContainerInitializers().add(initializerInfo); - }); + deploymentInfo.getHandlesTypesLoader().scanAnnotations(); + deploymentInfo.getHandlesTypesLoader().getInitializerClassMap().forEach((servletContainerInitializer, handlesTypes) -> { + ServletContainerInitializerInfo initializerInfo = new ServletContainerInitializerInfo(servletContainerInitializer, handlesTypes); + deploymentInfo.getServletContainerInitializers().add(initializerInfo); + }); + deploymentInfo.getHandlesTypesLoader().getAnnotations(WebListener.class).forEach(listener -> { + System.out.println(listener); + deploymentInfo.addEventListener(listener); + }); + deploymentInfo.getHandlesTypesLoader().getServlets().values().forEach(servletInfo -> { + ServletInfo webXmlInfo = deploymentInfo.getServlets().get(servletInfo.getServletName()); + if (webXmlInfo != null) { + servletInfo.getInitParams().forEach(webXmlInfo::addInitParam); + } else { + deploymentInfo.addServlet(servletInfo); + } + }); deploymentInfo.getHandlesTypesLoader().clear(); deploymentInfo.setHandlesTypesLoader(null); System.out.println("scanHandleTypes use :" + (System.currentTimeMillis() - start)); diff --git a/servlet-core/src/main/java/org/smartboot/servlet/WebXmlParseEngine.java b/servlet-core/src/main/java/org/smartboot/servlet/WebXmlParseEngine.java index 59a3ea38dd5ede6ea1dd0dd6cce592415f882450..690251806226b89fdd8f0dbc5cbe55137c30e08c 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/WebXmlParseEngine.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/WebXmlParseEngine.java @@ -163,11 +163,12 @@ class WebXmlParseEngine { NodeList rootNodeList = parentElement.getElementsByTagName("servlet"); for (int i = 0; i < rootNodeList.getLength(); i++) { Node node = rootNodeList.item(i); - Map nodeMap = getNodeValue(node, Arrays.asList("servlet-name", "servlet-class", "load-on-startup")); + Map nodeMap = getNodeValue(node, Arrays.asList("servlet-name", "servlet-class", "load-on-startup", "async-supported")); ServletInfo servletInfo = new ServletInfo(); servletInfo.setServletName(nodeMap.get("servlet-name")); servletInfo.setServletClass(nodeMap.get("servlet-class")); servletInfo.setLoadOnStartup(NumberUtils.toInt(nodeMap.get("load-on-startup"), 0)); + servletInfo.setAsyncSupported(Boolean.parseBoolean(nodeMap.get("async-supported"))); Map initParamMap = parseParam(node); initParamMap.forEach(servletInfo::addInitParam); servletInfo.setMultipartConfig(parseMultipartConfig(node)); @@ -197,15 +198,27 @@ class WebXmlParseEngine { return nodeMap; } + private List getNodeValues(Node node, String nodeName) { + NodeList nodeList = node.getChildNodes(); + List list = new ArrayList<>(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node childNode = nodeList.item(i); + if (childNode.getNodeName().equals(nodeName)) { + list.add(StringUtils.trim(childNode.getFirstChild().getNodeValue())); + } + } + return list; + } + /** * 解析Servlet配置 */ private void parseServletMapping(WebAppInfo webAppInfo, Element parentElement) { List childNodeList = getChildNode(parentElement, "servlet-mapping"); for (Node node : childNodeList) { - Map nodeData = getNodeValue(node, Arrays.asList("servlet-name", "url-pattern")); + Map nodeData = getNodeValue(node, Collections.singletonList("servlet-name")); ServletInfo servletInfo = webAppInfo.getServlet(nodeData.get("servlet-name")); - servletInfo.addMapping(nodeData.get("url-pattern")); + getNodeValues(node, "url-pattern").forEach(servletInfo::addMapping); } } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/conf/DeploymentInfo.java b/servlet-core/src/main/java/org/smartboot/servlet/conf/DeploymentInfo.java index 3d95bb9bfc55b894eb27f79c11cfb141501b8f5a..58e8ac2ab8636bcc6812dc242025af8be98a1c66 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/conf/DeploymentInfo.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/conf/DeploymentInfo.java @@ -10,13 +10,15 @@ package org.smartboot.servlet.conf; -import org.smartboot.servlet.HandlesTypesLoader; +import org.smartboot.servlet.AnnotationsLoader; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextListener; +import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestListener; import javax.servlet.annotation.HandlesTypes; +import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionListener; import java.net.URL; import java.util.ArrayList; @@ -42,12 +44,15 @@ public class DeploymentInfo { private List servletContextListeners = new ArrayList<>(); private List httpSessionListeners = new ArrayList<>(); private List servletRequestListeners = new ArrayList<>(); + + private List sessionAttributeListeners = new ArrayList<>(); + private List requestAttributeListeners = new ArrayList<>(); private List welcomeFiles = Collections.emptyList(); private ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); private String displayName; private URL contextUrl; - private HandlesTypesLoader handlesTypesLoader; + private AnnotationsLoader annotationsLoader; /** * 会话超时时间 */ @@ -73,19 +78,19 @@ public class DeploymentInfo { HandlesTypes handlesTypesAnnotation = servletContainerInitializer.getClass().getDeclaredAnnotation(HandlesTypes.class); if (handlesTypesAnnotation != null) { for (Class c : handlesTypesAnnotation.value()) { - handlesTypesLoader.add(servletContainerInitializer, c); + annotationsLoader.add(servletContainerInitializer, c); } } else { servletContainerInitializers.add(new ServletContainerInitializerInfo(servletContainerInitializer, null)); } } - public HandlesTypesLoader getHandlesTypesLoader() { - return handlesTypesLoader; + public AnnotationsLoader getHandlesTypesLoader() { + return annotationsLoader; } - public void setHandlesTypesLoader(HandlesTypesLoader handlesTypesLoader) { - this.handlesTypesLoader = handlesTypesLoader; + public void setHandlesTypesLoader(AnnotationsLoader annotationsLoader) { + this.annotationsLoader = annotationsLoader; } public List getServletContainerInitializers() { @@ -130,6 +135,12 @@ public class DeploymentInfo { if (servletRequestListeners.isEmpty()) { servletRequestListeners = Collections.emptyList(); } + if (sessionAttributeListeners.isEmpty()) { + sessionAttributeListeners = Collections.emptyList(); + } + if (requestAttributeListeners.isEmpty()) { + requestAttributeListeners = Collections.emptyList(); + } } public void addServletContextListener(ServletContextListener contextListener) { @@ -144,6 +155,14 @@ public class DeploymentInfo { servletRequestListeners.add(requestListener); } + public void addSessionAttributeListener(HttpSessionAttributeListener requestListener) { + sessionAttributeListeners.add(requestListener); + } + + public void addRequestAttributeListener(ServletRequestAttributeListener requestListener) { + requestAttributeListeners.add(requestListener); + } + public void addServletContextAttributeListener(ServletContextAttributeListener attributeListener) { servletContextAttributeListeners.add(attributeListener); } @@ -164,6 +183,14 @@ public class DeploymentInfo { return servletRequestListeners; } + public List getSessionAttributeListeners() { + return sessionAttributeListeners; + } + + public List getRequestAttributeListeners() { + return requestAttributeListeners; + } + public Map getFilters() { return filters; } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/conf/ServletInfo.java b/servlet-core/src/main/java/org/smartboot/servlet/conf/ServletInfo.java index e32886d70d3747dd066615f64517cd5b4d09e021..6491bb54257239ec1c89a14e2c0b1be332b09174 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/conf/ServletInfo.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/conf/ServletInfo.java @@ -36,6 +36,8 @@ public class ServletInfo { private boolean dynamic; private MultipartConfigElement multipartConfig; + private boolean asyncSupported; + public int getLoadOnStartup() { return loadOnStartup; } @@ -114,4 +116,12 @@ public class ServletInfo { public void setMultipartConfig(MultipartConfigElement multipartConfig) { this.multipartConfig = multipartConfig; } + + public boolean isAsyncSupported() { + return asyncSupported; + } + + public void setAsyncSupported(boolean asyncSupported) { + this.asyncSupported = asyncSupported; + } } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletRequestImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletRequestImpl.java index 6f7e18b9fce3bf9786a947a23155404cd9b246fb..c62f0399d921f2902293d6f8457e1ec2456840fb 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletRequestImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletRequestImpl.java @@ -25,6 +25,7 @@ import org.smartboot.servlet.third.commons.fileupload.FileItem; import org.smartboot.servlet.third.commons.fileupload.FileUpload; import org.smartboot.servlet.third.commons.fileupload.FileUploadException; import org.smartboot.servlet.third.commons.fileupload.disk.DiskFileItemFactory; +import org.smartboot.servlet.util.CollectionUtils; import org.smartboot.servlet.util.DateUtil; import javax.servlet.AsyncContext; @@ -35,6 +36,7 @@ import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletRequest; +import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @@ -559,13 +561,27 @@ public class HttpServletRequestImpl implements SmartHttpServletRequest { if (attributes == null) { attributes = new HashMap<>(); } - attributes.put(name, o); + + Object replace = attributes.put(name, o); + if (CollectionUtils.isNotEmpty(runtime.getDeploymentInfo().getRequestAttributeListeners())) { + ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(servletContext, this, name, o); + if (replace == null) { + runtime.getDeploymentInfo().getRequestAttributeListeners().forEach(request -> request.attributeAdded(event)); + } else { + runtime.getDeploymentInfo().getRequestAttributeListeners().forEach(request -> request.attributeReplaced(event)); + } + } } @Override public void removeAttribute(String name) { - if (attributes != null) { - attributes.remove(name); + if (attributes == null) { + return; + } + Object o = attributes.remove(name); + if (CollectionUtils.isNotEmpty(runtime.getDeploymentInfo().getRequestAttributeListeners())) { + ServletRequestAttributeEvent event = new ServletRequestAttributeEvent(servletContext, this, name, o); + runtime.getDeploymentInfo().getRequestAttributeListeners().forEach(request -> request.attributeRemoved(event)); } } @@ -643,7 +659,7 @@ public class HttpServletRequestImpl implements SmartHttpServletRequest { @Override public boolean isAsyncSupported() { - return false; + return servletInfo.isAsyncSupported(); } @Override diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletResponseImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletResponseImpl.java index 6185a27f350bd7ae9cfae30e7af8cce44ce4a0b2..8f5c9349b63873d4167d7b6d4afb521bc56d0dd5 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletResponseImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/HttpServletResponseImpl.java @@ -10,6 +10,9 @@ package org.smartboot.servlet.impl; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.smartboot.http.common.enums.HeaderNameEnum; import org.smartboot.http.common.enums.HttpStatus; import org.smartboot.http.common.logging.Logger; @@ -19,11 +22,9 @@ import org.smartboot.servlet.ServletContextRuntime; import org.smartboot.servlet.util.DateUtil; import org.smartboot.servlet.util.PathMatcherUtil; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Locale; @@ -39,10 +40,14 @@ public class HttpServletResponseImpl implements HttpServletResponse { private final HttpServletRequest request; private final ServletContextRuntime containerRuntime; private String contentType; + private boolean charsetSet = false; + private String charset; private PrintWriter writer; private ServletOutputStreamImpl servletOutputStream; private int bufferSize = -1; + private Locale locale; + public HttpServletResponseImpl(HttpServletRequest request, HttpResponse response, ServletContextRuntime containerRuntime) { this.request = request; this.response = response; @@ -51,15 +56,7 @@ public class HttpServletResponseImpl implements HttpServletResponse { @Override public void addCookie(Cookie cookie) { - org.smartboot.http.common.Cookie httpCookie = new org.smartboot.http.common.Cookie(cookie.getName(), cookie.getValue()); - httpCookie.setComment(cookie.getComment()); - httpCookie.setDomain(cookie.getDomain()); - httpCookie.setHttpOnly(cookie.isHttpOnly()); - httpCookie.setPath(cookie.getPath()); - httpCookie.setMaxAge(cookie.getMaxAge()); - httpCookie.setSecure(cookie.getSecure()); - httpCookie.setVersion(cookie.getVersion()); - response.addCookie(httpCookie); + response.setHeader(HeaderNameEnum.SET_COOKIE.getName(), cookie.toString()); } @Override @@ -185,23 +182,46 @@ public class HttpServletResponseImpl implements HttpServletResponse { @Override public String getCharacterEncoding() { - return response.getCharacterEncoding(); + if (charset != null) { + return charset; + } + return StandardCharsets.ISO_8859_1.name(); } @Override public void setCharacterEncoding(String charset) { - response.setCharacterEncoding(charset); + if (isCommitted()) { + return; + } + charsetSet = charset != null; + this.charset = charset; + if (contentType != null) { + response.setContentType(getContentType()); + } } @Override public String getContentType() { - return contentType; + if (contentType != null && charsetSet) { + return contentType + ";charset=" + getCharacterEncoding(); + } else { + return contentType; + } } @Override public void setContentType(String type) { - contentType = type; - response.setContentType(type); + if (isCommitted()) { + return; + } + int split = type.indexOf(";charset="); + if (split == -1) { + contentType = type; + response.setContentType(type); + } else { + contentType = type.substring(0, split); + setCharacterEncoding(type.substring(split + 10)); + } } @Override @@ -221,7 +241,7 @@ public class HttpServletResponseImpl implements HttpServletResponse { @Override public PrintWriter getWriter() throws IOException { if (writer == null) { - writer = new PrintWriter(new ServletPrintWriter(getOutputStream(), getCharacterEncoding(), containerRuntime)); + writer = new PrintWriter(new ServletPrintWriter(getOutputStream(), getCharacterEncoding())); } return writer; } @@ -296,11 +316,15 @@ public class HttpServletResponseImpl implements HttpServletResponse { @Override public Locale getLocale() { - throw new UnsupportedOperationException(); + return locale == null ? Locale.getDefault() : locale; } @Override public void setLocale(Locale loc) { - LOGGER.info("unSupport setLocal now"); + if (isCommitted()) { + return; + } + this.locale = loc; + setHeader(HeaderNameEnum.CONTENT_LANGUAGE.getName(), loc.getLanguage() + "-" + loc.getCountry()); } } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletContextImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletContextImpl.java index 457d1dd6fe728e07dc89a369f3b1a0e9bfea41b6..1b037208df40fb33e89f12c383ebacc786951485 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletContextImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletContextImpl.java @@ -33,10 +33,12 @@ import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; +import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestListener; import javax.servlet.SessionCookieConfig; import javax.servlet.SessionTrackingMode; import javax.servlet.descriptor.JspConfigDescriptor; +import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionListener; import java.io.File; import java.io.InputStream; @@ -63,7 +65,7 @@ public class ServletContextImpl implements ServletContext { private final ConcurrentMap attributes = new ConcurrentHashMap<>(); private final ServletContextRuntime containerRuntime; private final DeploymentInfo deploymentInfo; - private final SessionCookieConfig sessionCookieConfig = new SessionCookieConfigImpl(); + private final SessionCookieConfig sessionCookieConfig; private ServletContextPathType pathType = ServletContextPathType.PATH; /** * 请求执行管道 @@ -73,6 +75,7 @@ public class ServletContextImpl implements ServletContext { public ServletContextImpl(ServletContextRuntime containerRuntime) { this.containerRuntime = containerRuntime; this.deploymentInfo = containerRuntime.getDeploymentInfo(); + sessionCookieConfig = new SessionCookieConfigImpl(containerRuntime); } @Override @@ -301,7 +304,7 @@ public class ServletContextImpl implements ServletContext { @Override public String getServletContextName() { - return deploymentInfo.getDisplayName(); + return containerRuntime.getDisplayName(); } @Override @@ -328,6 +331,11 @@ public class ServletContextImpl implements ServletContext { return addServlet(servletName, createServlet(servletClass)); } + @Override + public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) { + return null; + } + @Override public T createServlet(Class clazz) { return newInstance(clazz); @@ -439,6 +447,10 @@ public class ServletContextImpl implements ServletContext { deploymentInfo.addServletContextAttributeListener((ServletContextAttributeListener) listener); } else if (HttpSessionListener.class.isAssignableFrom(listener.getClass())) { deploymentInfo.addHttpSessionListener((HttpSessionListener) listener); + } else if (HttpSessionAttributeListener.class.isAssignableFrom(listener.getClass())) { + deploymentInfo.addSessionAttributeListener((HttpSessionAttributeListener) listener); + } else if (ServletRequestAttributeListener.class.isAssignableFrom(listener.getClass())) { + deploymentInfo.addRequestAttributeListener((ServletRequestAttributeListener) listener); } else { throw new RuntimeException(listener.toString()); } @@ -483,6 +495,36 @@ public class ServletContextImpl implements ServletContext { throw new UnsupportedOperationException(); } + @Override + public int getSessionTimeout() { + return 0; + } + + @Override + public void setSessionTimeout(int sessionTimeout) { + + } + + @Override + public String getRequestCharacterEncoding() { + return null; + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + + } + + @Override + public String getResponseCharacterEncoding() { + return null; + } + + @Override + public void setResponseCharacterEncoding(String encoding) { + + } + public DeploymentInfo getDeploymentInfo() { return deploymentInfo; } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletOutputStreamImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletOutputStreamImpl.java index 50e3110da2665453df26a6547e30b3a3544dde3d..3e79cd789338b245ed0ee7baf25c98da4f6d777c 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletOutputStreamImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletOutputStreamImpl.java @@ -11,7 +11,6 @@ package org.smartboot.servlet.impl; import org.smartboot.http.common.BufferOutputStream; -import org.smartboot.socket.buffer.VirtualBuffer; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -78,10 +77,6 @@ public class ServletOutputStreamImpl extends ServletOutputStream { outputStream.write(b, off, len); } - public void write(VirtualBuffer buffer) throws IOException { - outputStream.write(buffer); - } - @Override public void close() throws IOException { outputStream.close(); diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletPrintWriter.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletPrintWriter.java index 0c2fce5306f5a0b5f99a2979d39495cd34f4d902..d551a2a47ddceec15844d1bce24feefdf9d0a62f 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletPrintWriter.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/ServletPrintWriter.java @@ -12,8 +12,6 @@ package org.smartboot.servlet.impl; import org.smartboot.http.common.logging.Logger; import org.smartboot.http.common.logging.LoggerFactory; -import org.smartboot.servlet.ServletContextRuntime; -import org.smartboot.socket.buffer.VirtualBuffer; import java.io.IOException; import java.io.Writer; @@ -30,12 +28,9 @@ public class ServletPrintWriter extends Writer { private static final Logger LOGGER = LoggerFactory.getLogger(ServletPrintWriter.class); private final ServletOutputStreamImpl servletOutputStream; private final CharsetEncoder charsetEncoder; - private final ServletContextRuntime containerRuntime; - private VirtualBuffer virtualBuffer; - public ServletPrintWriter(ServletOutputStreamImpl servletOutputStream, String charset, ServletContextRuntime containerRuntime) { + public ServletPrintWriter(ServletOutputStreamImpl servletOutputStream, String charset) { super(servletOutputStream); - this.containerRuntime = containerRuntime; this.servletOutputStream = servletOutputStream; this.charsetEncoder = Charset.forName(charset).newEncoder(); } @@ -58,36 +53,14 @@ public class ServletPrintWriter extends Writer { private void write(CharBuffer buffer) throws IOException { while (buffer.hasRemaining()) { - VirtualBuffer virtualBuffer; - //第一步:匹配VirtualBuffer - boolean committed = servletOutputStream.isCommitted(); - if (committed) { - //一个中文转成2个字节,预申请2倍空间 - virtualBuffer = containerRuntime.getMemoryPoolProvider().getBufferPage().allocate(buffer.remaining() < 32 ? 32 : buffer.remaining() << 1); - } else { - //未提交前写入暂存区 - if (this.virtualBuffer == null) { - this.virtualBuffer = VirtualBuffer.wrap(ByteBuffer.wrap(servletOutputStream.getBuffer())); - } - this.virtualBuffer.buffer().clear().position(servletOutputStream.getCount()); - virtualBuffer = this.virtualBuffer; - } + ByteBuffer virtualBuffer = ByteBuffer.allocate(buffer.remaining() < 32 ? 32 : buffer.remaining() << 1); //第二步:编码 - charsetEncoder.encode(buffer, virtualBuffer.buffer(), true); + charsetEncoder.encode(buffer, virtualBuffer, true); //第三步:输出 - virtualBuffer.buffer().flip(); - if (committed) { - servletOutputStream.write(virtualBuffer); - } else { - //更新缓冲区计数 - servletOutputStream.setCount(virtualBuffer.buffer().remaining()); - //释放内存 - if (servletOutputStream.isCommitted()) { - this.virtualBuffer = null; - } - } + virtualBuffer.flip(); + servletOutputStream.write(virtualBuffer.array(), 0, virtualBuffer.remaining()); if (buffer.hasRemaining()) { LOGGER.info("continue encoding ,remaining:" + buffer.remaining()); } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/impl/SessionCookieConfigImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/impl/SessionCookieConfigImpl.java index 2980022636601c8dab9641f5abfaa68256edabc9..1a02411b16867a719c0b923378a7c7bd7160520f 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/impl/SessionCookieConfigImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/impl/SessionCookieConfigImpl.java @@ -11,80 +11,107 @@ package org.smartboot.servlet.impl; import javax.servlet.SessionCookieConfig; +import org.smartboot.servlet.ServletContextRuntime; +import org.smartboot.servlet.provider.SessionProvider; /** * @author 三刀 * @version V1.0 , 2019/12/11 */ public class SessionCookieConfigImpl implements SessionCookieConfig { + private String name = SessionProvider.DEFAULT_SESSION_COOKIE_NAME; + private String path; + private String domain; + private int maxAge = -1; + private boolean secure; + private boolean httpOnly; + private String comment; + private final ServletContextRuntime servletContextRuntime; + + public SessionCookieConfigImpl(ServletContextRuntime servletContextRuntime) { + this.servletContextRuntime = servletContextRuntime; + } @Override public String getName() { - return null; + return name; } @Override public void setName(String name) { - + check(); + this.name = name; } @Override public String getDomain() { - return null; + return domain; } @Override public void setDomain(String domain) { - + check(); + this.domain = domain; } @Override public String getPath() { - return null; + return path; + } + + private void check() { + if (servletContextRuntime.isStarted()) { + throw new IllegalStateException("sessionCookie can not be set after the web application has started"); + } } @Override public void setPath(String path) { - + check(); + this.path = path; } @Override public String getComment() { - return null; + return comment; } @Override public void setComment(String comment) { - + check(); + this.comment = comment; } @Override public boolean isHttpOnly() { - return false; + return httpOnly; } @Override public void setHttpOnly(boolean httpOnly) { - + check(); + this.httpOnly = httpOnly; } @Override public boolean isSecure() { - return false; + return secure; } @Override public void setSecure(boolean secure) { - + check(); + this.secure = secure; } @Override public int getMaxAge() { - return 0; + return maxAge; } @Override public void setMaxAge(int maxAge) { - + check(); + this.maxAge = maxAge; } } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/HttpSessionImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/HttpSessionImpl.java index d86aaa34ec20982d6d69cc6c5c60a59f2abbabae..d02441ce139128377aeb68744fc1ca9d5b56c236 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/HttpSessionImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/HttpSessionImpl.java @@ -11,9 +11,11 @@ package org.smartboot.servlet.plugins.session; import org.smartboot.servlet.impl.ServletContextImpl; +import org.smartboot.servlet.util.CollectionUtils; import javax.servlet.ServletContext; import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; import javax.servlet.http.HttpSessionContext; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; @@ -115,7 +117,15 @@ class HttpSessionImpl implements HttpSession { @Override public void setAttribute(String name, Object value) { checkState(); - attributes.put(name, value); + Object replace = attributes.put(name, value); + if (CollectionUtils.isNotEmpty(servletContext.getDeploymentInfo().getSessionAttributeListeners())) { + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, value); + if (replace == null) { + servletContext.getDeploymentInfo().getSessionAttributeListeners().forEach(request -> request.attributeAdded(event)); + } else { + servletContext.getDeploymentInfo().getSessionAttributeListeners().forEach(request -> request.attributeReplaced(event)); + } + } } @Override @@ -127,7 +137,11 @@ class HttpSessionImpl implements HttpSession { @Override public void removeAttribute(String name) { checkState(); - attributes.remove(name); + Object o = attributes.remove(name); + if (CollectionUtils.isNotEmpty(servletContext.getDeploymentInfo().getSessionAttributeListeners())) { + HttpSessionBindingEvent event = new HttpSessionBindingEvent(this, name, o); + servletContext.getDeploymentInfo().getSessionAttributeListeners().forEach(request -> request.attributeRemoved(event)); + } } @Override diff --git a/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/SessionProviderImpl.java b/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/SessionProviderImpl.java index a86c8fb34c535dfd5c6811be72888fb5651b3657..ea95b8a7c4325c014bd51712a9d2ebaf0453cd50 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/SessionProviderImpl.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/plugins/session/SessionProviderImpl.java @@ -10,14 +10,15 @@ package org.smartboot.servlet.plugins.session; -import org.smartboot.servlet.impl.HttpServletRequestImpl; -import org.smartboot.servlet.provider.SessionProvider; - +import javax.servlet.SessionCookieConfig; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionContext; +import org.smartboot.servlet.impl.HttpServletRequestImpl; +import org.smartboot.servlet.provider.SessionProvider; + import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -48,7 +49,7 @@ class SessionProviderImpl implements SessionProvider, HttpSessionContext { public void clearExpireSession() { List list = new ArrayList<>(sessionMap.values()); list.stream().filter(httpSession -> httpSession.getMaxInactiveInterval() > 0 - && httpSession.getLastAccessedTime() + httpSession.getMaxInactiveInterval() > System.currentTimeMillis()) + && httpSession.getLastAccessedTime() + httpSession.getMaxInactiveInterval() > System.currentTimeMillis()) .forEach(httpSession -> { try { httpSession.invalid(); @@ -83,8 +84,16 @@ class SessionProviderImpl implements SessionProvider, HttpSessionContext { //该sessionId生成策略缺乏安全性,后续重新设计 httpSession = new HttpSessionImpl(this, String.valueOf(System.currentTimeMillis()), request.getServletContext()); httpSession.setMaxInactiveInterval(maxInactiveInterval); - Cookie cookie = new Cookie(DEFAULT_SESSION_COOKIE_NAME, httpSession.getId()); - cookie.setPath(httpSession.getServletContext().getContextPath()); + SessionCookieConfig sessionCookieConfig = request.getServletContext().getSessionCookieConfig(); + Cookie cookie = new Cookie(sessionCookieConfig.getName(), httpSession.getId()); + cookie.setPath(request.getRequestURI()); + cookie.setComment(sessionCookieConfig.getComment()); + if (sessionCookieConfig.getDomain() != null) { + cookie.setDomain(sessionCookieConfig.getDomain()); + } + cookie.setHttpOnly(sessionCookieConfig.isHttpOnly()); + cookie.setSecure(sessionCookieConfig.isSecure()); + cookie.setMaxAge(sessionCookieConfig.getMaxAge()); response.addCookie(cookie); sessionMap.put(httpSession.getId(), httpSession); } diff --git a/servlet-core/src/main/java/org/smartboot/servlet/util/PathMatcherUtil.java b/servlet-core/src/main/java/org/smartboot/servlet/util/PathMatcherUtil.java index 93410ef35378a802b1be47370f7cd2910e7a6743..3d3329e9627ddfa002a933eb9a3d2dfef859758b 100644 --- a/servlet-core/src/main/java/org/smartboot/servlet/util/PathMatcherUtil.java +++ b/servlet-core/src/main/java/org/smartboot/servlet/util/PathMatcherUtil.java @@ -92,15 +92,21 @@ public class PathMatcherUtil { } //舍去"/ab/*"最后一位"/*" int matchLen = pattern.length() - 2; - if (uri.length() - startIndex < matchLen) { + int remainingLen = uri.length() - startIndex; + if (remainingLen < matchLen) { return -1; } + if (remainingLen >= matchLen + 1 && uri.charAt(startIndex + matchLen) != '/') { + return -1; + } + //第一位肯定是"/",从第二位开始匹配 for (int i = 1; i < matchLen; i++) { if (uri.charAt(startIndex + i) != pattern.charAt(i)) { return -1; } } + servletPathEndIndex = startIndex + pattern.length() - 2; break; //后缀匹配 diff --git a/spring-boot-starter/pom.xml b/spring-boot-starter/pom.xml index cc209f8368729becde33650d6d9553ab9c4a5173..a8884ccd80826f723b670a9da2ce514952ae4d2f 100644 --- a/spring-boot-starter/pom.xml +++ b/spring-boot-starter/pom.xml @@ -28,7 +28,7 @@ org.springframework.boot spring-boot - 2.7.5 + 2.7.11 true diff --git a/testsuite/pom.xml b/testsuite/pom.xml index cb4d6d52ebdf9532f762d7ec8a4a7e4c8fffea34..51111de505db038db864ab6ea164a8902a8f4e3c 100644 --- a/testsuite/pom.xml +++ b/testsuite/pom.xml @@ -18,65 +18,294 @@ testsuite war - 8 - 8 + 10.0.0-SNAPSHOT + 1.7.0.Alpha14 + 5.9.2 + 3.1.4 + 3.0.0-M7 + 3.1.8.Final + 2.0 + 2.2 + 2.2 + 4.0.1 + 2.1.214 + 2.0.0 + 2.0.6 + 11 + 11 + + -Xmx8g -Xms4g + - com.alibaba - fastjson - 1.2.83 + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + org.smartboot.servlet + servlet-core + 0.2.1 + + + org.junit + junit-bom + 5.9.3 + pom + import + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-bom + ${version.shrinkwrap} + test + pom + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-depchain + ${version.shrinkwrap} + pom + test + + + org.jboss.arquillian.container + arquillian-container-spi + ${version.arquillian_core} + + + org.jboss.arquillian.container + arquillian-container-test-spi + ${version.arquillian_core} + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + ${version.arquillian_core} + test + + + + org.jboss.weld.servlet + weld-servlet-core + ${version.weld} + test + + + javax.enterprise + cdi-api + ${version.cdi-api} + test + + + org.glassfish.web + el-impl + ${version.glassfish.el} + test + + + javax.servlet.jsp + jsp-api + ${version.jsp-api} + test + + + javax.servlet + javax.servlet-api + ${version.servlet-api} + provided + + + com.h2database + h2 + ${version.h2} + test org.slf4j slf4j-api - 1.7.32 + ${slf4j.version} + + + org.slf4j + jcl-over-slf4j + ${slf4j.version} + + + org.slf4j + jul-to-slf4j + ${slf4j.version} org.slf4j slf4j-simple - 1.7.32 + ${slf4j.version} - org.smartboot.http - smart-http-client - 1.1.11-SNAPSHOT + org.hamcrest + hamcrest + 2.2 - javax.servlet - servlet-api - 2.5 - provided + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + org.junit.vintage + junit-vintage-engine + ${junit.version} + test + - junit - junit - 4.13.2 + org.junit.jupiter + junit-jupiter-engine + ${junit.version} test + + jakartatck + servlet + ${jakarta.tck.version} + + + commons-logging + * + + + org.glassfish.metro + * + + + + + jakartatck + libutil + ${jakarta.tck.version} + + + org.glassfish.metro + * + + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.2.0 + + + org.apache.maven.plugins + maven-resources-plugin + 3.3.0 + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + org.apache.maven.plugins + maven-surefire-plugin + ${surefire.version} + + + ${fork.argLine} -Duser.language=en -Duser.country=US + -Djava.locale.providers=COMPAT,CLDR + -Djava.protocol.handler.pkgs=javax.net.ssl + -Djavax.net.ssl.keyStore=${project.build.testOutputDirectory}/certificates/clientcert.jks + -Djavax.net.ssl.keyStorePassword=changeit + -Djavax.net.ssl.trustStore=${project.build.directory}/cacerts.jks + + 15000 + + jakartatck:servlet + + + ${http2.timeout} + + + **/URLClient* + **/Client* + + + false + + + + org.basepom.maven + duplicate-finder-maven-plugin + 1.5.1 + + + - org.apache.tomcat.maven - tomcat7-maven-plugin - 2.2 - - UTF8 - UTF-8 - /demo - 8082 - + org.apache.maven.plugins + maven-dependency-plugin + 3.3.0 + + + unpack + generate-test-resources + + unpack + + + + + jakartatck + libutil + jar + false + ${project.build.testOutputDirectory}/ + **/**cts_cert,**/**clientcert.jks,**/**clientcert.p12 + + + + + - org.smartboot.servlet - smart-servlet-maven-plugin - 0.2.1 + org.codehaus.mojo + keytool-maven-plugin + 1.6 + + + + importCertificate + + process-test-resources + + - 8081 - /demo + + true + cts + CN=CTS, OU=Java Software, O=Sun Microsystems Inc., L=Burlington, ST=MA, C=US + ${project.build.testOutputDirectory}/certificates/cts_cert + JKS + ${project.build.directory}/cacerts.jks + changeit + changeit + true diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/AbstractJettyEmbeddedConfiguration.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/AbstractJettyEmbeddedConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..780bb6f119f8ca8e27daaae823c8cde306bc6119 --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/AbstractJettyEmbeddedConfiguration.java @@ -0,0 +1,286 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.smartboot.servlet.testsuite; + +import org.jboss.arquillian.container.spi.ConfigurationException; +import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@link ContainerConfiguration} common base for the Jetty Embedded + * containers + * + * @author Dan Allen + * @author Ales Justin + * @author Alex Soto + */ +public abstract class AbstractJettyEmbeddedConfiguration implements ContainerConfiguration { + private String bindAddress = "localhost"; + + private int bindHttpPort = 9090; + + private Map mimeTypes; + + private Map inferredEncodings; + + private int headerBufferSize = 0; + + private File realmProperties; + + /** + * List of server configuration classes that can be used for + * establishing the configuration tasks for the WebApp being deployed. + */ + private String configurationClasses; + + private String requestCookieCompliance; + + private String responseCookieCompliance; + + private boolean useArchiveNameAsContext; + + private boolean ssl; + + private boolean h2cEnabled; + + /** + * Path to keystore file + */ + private String keystorePath; + + private String keystorePassword; + + private String trustStorePath; + + private String trustStorePassword; + + private boolean sniRequired; + + private boolean sniHostCheck; + + private boolean needClientAuth; + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.ContainerConfiguration#validate() + */ + public void validate() throws ConfigurationException { + if (this.realmProperties != null) { + if (!this.realmProperties.exists()) { + throw new ConfigurationException( + String.format("Realm properties file %s must exists.", this.realmProperties.getAbsolutePath())); + } + if (this.realmProperties.isDirectory()) { + throw new ConfigurationException("Realm Properties should be a file and not a directory"); + } + } + } + + public int getBindHttpPort() { + return bindHttpPort; + } + + public void setBindHttpPort(int bindHttpPort) { + this.bindHttpPort = bindHttpPort; + } + + public String getBindAddress() { + return bindAddress; + } + + public void setBindAddress(String bindAddress) { + this.bindAddress = bindAddress; + } + + public String getConfigurationClasses() { + return configurationClasses; + } + + /** + * @param configurationClasses A comma separated list of fully qualified configuration classes + */ + public void setConfigurationClasses(String configurationClasses) { + this.configurationClasses = configurationClasses; + } + + public int getHeaderBufferSize() { + return this.headerBufferSize; + } + + public boolean isHeaderBufferSizeSet() { + return this.headerBufferSize > 0; + } + + public void setHeaderBufferSize(int headerBufferSize) { + this.headerBufferSize = headerBufferSize; + } + + public void setRealmProperties(String realmProperties) { + this.realmProperties = new File(realmProperties); + } + + public boolean isRealmPropertiesFileSet() { + return this.realmProperties != null; + } + + public File getRealmProperties() { + return realmProperties; + } + + public void setMimeTypes(String mimeTypes) { + this.mimeTypes = new HashMap<>(); + String[] splittedLines = mimeTypes.split(" "); + for (int i = 0; i < splittedLines.length; i += 2) { + if (i + 1 >= splittedLines.length) { + throw new ConfigurationException(String.format( + "Mime Type definition should follow the format [ ]*, for example js application/javascript but %s definition has been found.", + mimeTypes)); + } + this.mimeTypes.put(splittedLines[i], splittedLines[i + 1]); + } + } + + public boolean areMimeTypesSet() { + return this.mimeTypes != null; + } + + public Map getMimeTypes() { + return mimeTypes; + } + + public String getRequestCookieCompliance() { + return requestCookieCompliance; + } + + public void setRequestCookieCompliance(String requestCookieCompliance) { + this.requestCookieCompliance = requestCookieCompliance; + } + + public String getResponseCookieCompliance() { + return responseCookieCompliance; + } + + public void setResponseCookieCompliance(String responseCookieCompliance) { + this.responseCookieCompliance = responseCookieCompliance; + } + + public boolean isUseArchiveNameAsContext() { + return useArchiveNameAsContext; + } + + public void setUseArchiveNameAsContext(boolean useArchiveNameAsContext) { + this.useArchiveNameAsContext = useArchiveNameAsContext; + } + + public void setInferredEncodings(String inferredEncodings) { + this.inferredEncodings = new HashMap<>(); + String[] splittedLines = inferredEncodings.split(" "); + for (int i = 0; i < splittedLines.length; i += 2) { + if (i + 1 >= splittedLines.length) { + throw new ConfigurationException(String.format( + "Mime Type definition should follow the format [ ]*, for example js application/javascript but %s definition has been found.", + inferredEncodings)); + } + this.inferredEncodings.put(splittedLines[i], splittedLines[i + 1]); + } + } + + public boolean areInferredEncodings() { + return this.inferredEncodings != null; + } + + public Map getInferredEncodings() { + return inferredEncodings; + } + + public boolean isSsl() { + return ssl; + } + + public void setSsl(boolean ssl) { + this.ssl = ssl; + } + + public String getKeystorePath() { + return keystorePath; + } + + public void setKeystorePath(String keystorePath) { + this.keystorePath = keystorePath; + } + + public String getKeystorePassword() { + return keystorePassword; + } + + public void setKeystorePassword(String keystorePassword) { + this.keystorePassword = keystorePassword; + } + + public String getTrustStorePath() { + return trustStorePath; + } + + public void setTrustStorePath(String trustStorePath) { + this.trustStorePath = trustStorePath; + } + + public String getTrustStorePassword() { + return trustStorePassword; + } + + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + public boolean isSniRequired() { + return sniRequired; + } + + public void setSniRequired(boolean sniRequired) { + this.sniRequired = sniRequired; + } + + public boolean isSniHostCheck() { + return sniHostCheck; + } + + public void setSniHostCheck(boolean sniHostCheck) { + this.sniHostCheck = sniHostCheck; + } + + public boolean isNeedClientAuth() { + return needClientAuth; + } + + public void setNeedClientAuth(boolean needClientAuth) { + this.needClientAuth = needClientAuth; + } + + public boolean isH2cEnabled() { + return h2cEnabled; + } + + public void setH2cEnabled(boolean h2cEnabled) { + this.h2cEnabled = h2cEnabled; + } +} + diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/ArquillianAppProvider.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/ArquillianAppProvider.java new file mode 100644 index 0000000000000000000000000000000000000000..ba7f64bc43022bed015b22599183c33878d15fd4 --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/ArquillianAppProvider.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ + +package org.smartboot.servlet.testsuite; + +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.smartboot.servlet.ContainerRuntime; +import org.smartboot.servlet.ServletContextRuntime; +import org.smartboot.servlet.util.WarUtil; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.logging.Logger; + +public class ArquillianAppProvider { + private static final Logger LOG = Logger.getLogger(ArquillianAppProvider.class.getName()); + + /** + * The prefix assigned to the temporary file where the archive is exported + */ + private static final String EXPORT_FILE_PREFIX = "export"; + + /** + * Directory into which we'll extract export the war files + */ + private static final File EXPORT_DIR; + + static { + /* + * Use of java.io.tmpdir Should be a last-resort fallback for temp directory. + * + * Use of java.io.tmpdir on CI systems is dangerous (overwrite possibility is extremely high) + * + * Use of java.io.tmpdir on Unix systems is unreliable (due to common /tmp dir cleanup processes) + */ + File systemDefaultTmpDir = new File(System.getProperty("java.io.tmpdir")); + + // If running under maven + surefire, use information provided by surefire. + String baseDirVal = System.getProperty("basedir"); + + File mavenTmpDir = null; + if (baseDirVal != null) { + File baseDir = new File(baseDirVal); + if (baseDir.exists() && baseDir.isDirectory()) { + File targetDir = new File(baseDir, "target"); + if (targetDir.exists() && targetDir.isDirectory()) { + mavenTmpDir = new File(targetDir, "arquillian-jetty-temp"); + mavenTmpDir.mkdirs(); + } + } + } + + if ((mavenTmpDir != null) && mavenTmpDir.exists() && mavenTmpDir.isDirectory()) { + EXPORT_DIR = mavenTmpDir; + } else { + EXPORT_DIR = systemDefaultTmpDir; + } + + // If the temp location doesn't exist or isn't a directory + if (!EXPORT_DIR.exists() || !EXPORT_DIR.isDirectory()) { + throw new IllegalStateException("Could not obtain export directory \"" + EXPORT_DIR.getAbsolutePath() + "\""); + } + } + + private final JettyEmbeddedConfiguration config; + private ContainerRuntime deploymentManager; + + public ArquillianAppProvider(JettyEmbeddedConfiguration config) { + this.config = config; + } + + protected ServletContextRuntime createApp(ContainerRuntime containerRuntime, final Archive archive) throws Exception { + String name = archive.getName(); + int extOff = name.lastIndexOf('.'); + if (extOff <= 0) { + throw new RuntimeException("Not a valid Web Archive filename: " + name); + } + String ext = name.substring(extOff).toLowerCase(); + if (!ext.equals(".war")) { + throw new RuntimeException("Not a recognized Web Archive: " + name); + } + name = name.substring(0, extOff); + + final File exported; + try { + if (this.config.isUseArchiveNameAsContext()) { + Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); + Path archivePath = tmpDirectory.resolveSibling(archive.getName()); + Files.deleteIfExists(archivePath); + exported = Files.createFile(archivePath).toFile(); + exported.deleteOnExit(); + } else { + // If this method returns successfully then it is guaranteed that: + // 1. The file denoted by the returned abstract pathname did not exist before this method was invoked, and + // 2. Neither this method nor any of its variants will return the same abstract pathname again in the current invocation of the virtual machine. + exported = File.createTempFile(EXPORT_FILE_PREFIX, archive.getName(), EXPORT_DIR); + } + } catch (IOException e) { + throw new RuntimeException("Could not create temporary File in " + EXPORT_DIR + " to write exported archive", + e); + } + // We are overwriting the temporary file placeholder reserved by File#createTemplateFile() + archive.as(ZipExporter.class).exportTo(exported, true); + + // Mark to delete when we come down + // exported.deleteOnExit(); + + // Add the context + URI uri = exported.toURI(); + LOG.info("Webapp archive location: " + uri.toASCIIString()); + File dirFile = new File(exported.getParentFile(), name); + System.out.println("开始解压[" + name + "]..."); + WarUtil.unZip(exported, dirFile); + return containerRuntime.addRuntime(dirFile.getAbsolutePath(), "/" + name); + } + +} diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/DemoServlet.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/DemoServlet.java deleted file mode 100644 index 06a9d62d399249ad5e06bc6abc2c805852b3df66..0000000000000000000000000000000000000000 --- a/testsuite/src/main/java/org/smartboot/servlet/testsuite/DemoServlet.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017-2021, org.smartboot. All rights reserved. - * project name: smart-servlet - * file name: DemoServlet.java - * Date: 2021-05-14 - * Author: sandao (zhengjunweimail@163.com) - * - */ -package org.smartboot.servlet.testsuite; - -import com.alibaba.fastjson.JSONObject; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Enumeration; - -/** - * @author 三刀(zhengjunweimail@163.com) - * @version V1.0 , 2021/5/14 - */ -public class DemoServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - JSONObject jsonObject = new JSONObject(); - jsonObject.put("contextPath", req.getContextPath()); - jsonObject.put("servletPath", req.getServletPath()); - jsonObject.put("pathInfo", req.getPathInfo()); - jsonObject.put("queryString", req.getQueryString()); - Enumeration enumeration = req.getParameterNames(); - while ((enumeration.hasMoreElements())) { - String param = String.valueOf(enumeration.nextElement()); - jsonObject.put("param_" + param, req.getParameter(param)); - } - resp.getOutputStream().write((jsonObject.toJSONString()).getBytes(StandardCharsets.UTF_8)); - } -} diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedConfiguration.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..09aa9ab368dc565860f51514d9b937151fa8cbf3 --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedConfiguration.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ +package org.smartboot.servlet.testsuite; + + +import org.smartboot.http.server.HttpServerConfiguration; + +import java.io.File; +import java.net.URI; + +/** + * A {@link org.jboss.arquillian.container.spi.client.container.ContainerConfiguration} implementation for the Jetty Embedded + * containers. + * + * @author Dan Allen + * @author Ales Justin + */ +public class JettyEmbeddedConfiguration extends AbstractJettyEmbeddedConfiguration { + public enum ClassLoaderBehavior { + /** + * Default behavior for Java Spec (server classloader, then webapp). + *

+ * Also the default for Arquillian. + */ + JAVA_SPEC, + /** + * Default behavior for Servlet Spec (webapp classloader, then server) + */ + SERVLET_SPEC + } + + /** + * Classloader Search Order behavior. + *

+ */ + private ClassLoaderBehavior classloaderBehavior = ClassLoaderBehavior.JAVA_SPEC; + + /** + * Optional override for the default servlet spec descriptor + */ + private URI defaultsDescriptor; + + /** + * Dump, to System.err, the server state tree after the server has successfully started up. + */ + private boolean dumpServerAfterStart = false; + + /** + * Optional HttpConfiguration for the ServerConnector that Arquillian + * creates. + */ + private HttpServerConfiguration httpConfiguration; + + /** + * Idle Timeout (in milliseconds) for active connections. + *

+ * Default: 30,000ms + */ + private long idleTimeoutMillis = 30000; + + /** + * Base directory for all temp files that Jetty will manage. + */ + private File tempDirectory; + + public ClassLoaderBehavior getClassloaderBehavior() { + return classloaderBehavior; + } + + public URI getDefaultsDescriptor() { + return defaultsDescriptor; + } + + public HttpServerConfiguration getHttpConfiguration() { + return httpConfiguration; + } + + public long getIdleTimeoutMillis() { + return idleTimeoutMillis; + } + + public File getTempDirectory() { + return tempDirectory; + } + + public boolean hasDefaultsDescriptor() { + return (defaultsDescriptor != null); + } + + public boolean isDumpServerAfterStart() { + return dumpServerAfterStart; + } + + public void setClassloaderBehavior(ClassLoaderBehavior classloaderBehavior) { + this.classloaderBehavior = classloaderBehavior; + } + + public void setDefaultsDescriptor(URI defaultsDescriptor) { + this.defaultsDescriptor = defaultsDescriptor; + } + + public void setDumpServerAfterStart(boolean serverDumpAfterStart) { + this.dumpServerAfterStart = serverDumpAfterStart; + } + + public void setHttpConfiguration(HttpServerConfiguration httpConfiguration) { + this.httpConfiguration = httpConfiguration; + } + + public void setIdleTimeoutMillis(long milliseconds) { + this.idleTimeoutMillis = milliseconds; + } + + public void setTempDirectory(File tempDirectory) { + this.tempDirectory = tempDirectory; + } +} diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedContainer.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedContainer.java new file mode 100644 index 0000000000000000000000000000000000000000..204ad637cf7018af5af42617989a0aa6f7acea1b --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/JettyEmbeddedContainer.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ +package org.smartboot.servlet.testsuite; + +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.container.spi.client.container.DeploymentException; +import org.jboss.arquillian.container.spi.client.container.LifecycleException; +import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; +import org.jboss.arquillian.container.spi.client.protocol.metadata.HTTPContext; +import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; +import org.jboss.arquillian.container.spi.client.protocol.metadata.Servlet; +import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; +import org.jboss.arquillian.core.api.InstanceProducer; +import org.jboss.arquillian.core.api.annotation.ApplicationScoped; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.descriptor.api.Descriptor; +import org.smartboot.http.common.utils.Mimetypes; +import org.smartboot.http.server.*; +import org.smartboot.http.server.impl.WebSocketRequestImpl; +import org.smartboot.http.server.impl.WebSocketResponseImpl; +import org.smartboot.servlet.ContainerRuntime; +import org.smartboot.servlet.ServletContextRuntime; +import org.smartboot.servlet.conf.ServletInfo; + +import javax.servlet.ServletContext; +import java.io.File; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +public class JettyEmbeddedContainer implements DeployableContainer { + private static final Logger log = Logger.getLogger(JettyEmbeddedContainer.class.getName()); + + private HttpBootstrap bootstrap; + private ContainerRuntime containerRuntime; + private ArquillianAppProvider appProvider; + + private JettyEmbeddedConfiguration containerConfig; + + private String listeningHost; + private int listeningPort; + + @Inject + @DeploymentScoped + private InstanceProducer webAppContextProducer; + + @Inject + @ApplicationScoped + private InstanceProducer servletContextInstanceProducer; + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#getConfigurationClass() + */ + public Class getConfigurationClass() { + return JettyEmbeddedConfiguration.class; + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#getDefaultProtocol() + */ + public ProtocolDescription getDefaultProtocol() { + // Jetty 9 is a Servlet 3.1 container. + // However, Arquillian "Protocol" actuall means "Packaging" + // TODO: Fix to servlet 3.1 (when available in arquillian) + return new ProtocolDescription("Servlet 3.0"); + } + + public void setup(JettyEmbeddedConfiguration containerConfig) { + this.containerConfig = containerConfig; + } + + public void start() throws LifecycleException { + appProvider = new ArquillianAppProvider(containerConfig); + this.bootstrap = new HttpBootstrap(); + containerRuntime = new ContainerRuntime(); + bootstrap.httpHandler(new HttpServerHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response) { + containerRuntime.doHandle(request, response); + } + }).webSocketHandler(new WebSocketHandler() { + @Override + public void whenHeaderComplete(WebSocketRequestImpl request, WebSocketResponseImpl response) { + containerRuntime.onHeaderComplete(request.getRequest()); + } + + @Override + public void handle(WebSocketRequest request, WebSocketResponse response) { + containerRuntime.doHandle(request, response); + } + }); + bootstrap.configuration().bannerEnabled(false).readBufferSize(1024 * 1024).debug(true); + + containerRuntime.start(this.bootstrap.configuration()); + bootstrap.setPort(containerConfig.getBindHttpPort()).start(); + listeningHost = "127.0.0.1"; +// listeningHost = containerConfig.getBindAddress(); + listeningPort = containerConfig.getBindHttpPort(); + System.out.println("host: " + listeningHost + " port:" + listeningPort); + } + + private String getRealmName() { + File realmProperties = containerConfig.getRealmProperties(); + String fileName = realmProperties.getName(); + int index; + if ((index = fileName.indexOf('.')) > -1) { + fileName = fileName.substring(0, index); + } + return fileName; + } + + public void stop() throws LifecycleException { + try { + System.out.println("stop....."); + containerRuntime.stop(); + bootstrap.shutdown(); + } catch (Exception e) { + throw new LifecycleException("Could not stop container", e); + } + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#deploy(org.jboss.shrinkwrap.descriptor.api.Descriptor) + */ + public void deploy(Descriptor descriptor) throws DeploymentException { + throw new UnsupportedOperationException("Not implemented"); + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#undeploy(org.jboss.shrinkwrap.descriptor.api.Descriptor) + */ + public void undeploy(Descriptor descriptor) throws DeploymentException { + throw new UnsupportedOperationException("Not implemented"); + } + + public ProtocolMetaData deploy(final Archive archive) throws DeploymentException { + try { + ServletContextRuntime app = appProvider.createApp(containerRuntime,archive); + + app.start(); + + webAppContextProducer.set(app); + servletContextInstanceProducer.set(app.getServletContext()); + + HTTPContext httpContext = new HTTPContext(listeningHost, listeningPort); + for (ServletInfo servlet : app.getDeploymentInfo().getServlets().values()) { + httpContext.add(new Servlet(servlet.getServletName(), app.getContextPath())); + } + return new ProtocolMetaData().addContext(httpContext); + } catch (Exception e) { + throw new DeploymentException("Could not deploy " + archive.getName(), e); + } + } + + private Mimetypes getMimeTypes() { + Map configuredMimeTypes = containerConfig.getMimeTypes(); + Set> entries = configuredMimeTypes.entrySet(); +// MimeTypes mimeTypes = new MimeTypes(); +// entries.forEach(stringStringEntry -> +// mimeTypes.addMimeMapping(stringStringEntry.getKey(), stringStringEntry.getValue())); + return Mimetypes.getInstance(); + } + + public void undeploy(Archive archive) throws DeploymentException { + ServletContextRuntime app = webAppContextProducer.get(); + if (app != null) { + app.stop(); + } + } +} diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/SmartServletExtension.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/SmartServletExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..dcb1896faa2e1e7e208ecc10d3d3052424988408 --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/SmartServletExtension.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ +package org.smartboot.servlet.testsuite; + +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.core.spi.LoadableExtension; + +/** + * Jetty Embedded 10.x extension. + * + * @author Aslak Knutsen + */ +public class SmartServletExtension implements LoadableExtension { + @Override + public void register(ExtensionBuilder builder) { + builder.service(DeployableContainer.class, JettyEmbeddedContainer.class); + } +} diff --git a/testsuite/src/main/java/org/smartboot/servlet/testsuite/VersionUtil.java b/testsuite/src/main/java/org/smartboot/servlet/testsuite/VersionUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..d8419711cb50a2b3f58cdd70c9104a5a83d52d0f --- /dev/null +++ b/testsuite/src/main/java/org/smartboot/servlet/testsuite/VersionUtil.java @@ -0,0 +1,93 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.smartboot.servlet.testsuite; + +/** + * VersionUtil + * + * @author Aslak Knutsen + + */ +public class VersionUtil { + private VersionUtil() { + } + + public static class Version implements Comparable { + private final Integer major; + private final Integer minor; + + public Version(int major, int minor) { + this.major = major; + this.minor = minor; + } + + /** + * @return the major + */ + public int getMajor() { + return major; + } + + /** + * @return the minor + */ + public int getMinor() { + return minor; + } + + /* (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + @Override + public int compareTo(Version o) { + int majorCompare = major.compareTo(o.major); + if (majorCompare == 0) { + return minor.compareTo(o.minor); + } + return majorCompare; + } + } + + private static final String expression = "([0-9]{1,5})\\.([0-9]{1,5}).*"; + + public static Version extract(String version) { + if (version == null || !version.matches(expression)) { + return new Version(0, 0); + } + + return new Version( + Integer.parseInt(version.replaceAll(expression, "$1")), + Integer.parseInt(version.replaceAll(expression, "$2"))); + } + + public static boolean isGreaterThenOrEqual(String greater, String then) { + return isGreaterThenOrEqual(extract(greater), extract(then)); + } + + public static boolean isGreaterThenOrEqual(Version greater, Version then) { + return greater.compareTo(then) >= 0; + } + + public static boolean isLessThenOrEqual(String less, String then) { + return isLessThenOrEqual(extract(less), extract(then)); + } + + public static boolean isLessThenOrEqual(Version less, Version then) { + return less.compareTo(then) <= 0; + } +} diff --git a/testsuite/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/testsuite/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 0000000000000000000000000000000000000000..1b3da30ebbfda68bcf3c33c670c368b58594ca79 --- /dev/null +++ b/testsuite/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.smartboot.servlet.testsuite.SmartServletExtension \ No newline at end of file diff --git a/testsuite/src/main/webapp/WEB-INF/web.xml b/testsuite/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index afba978e10df27e4a100dd58befb5d17b8238fa1..0000000000000000000000000000000000000000 --- a/testsuite/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - demoServlet - org.smartboot.servlet.testsuite.DemoServlet - - - demoServlet - /demo - - - demoServlet - /pathMatch/* - - - demoServlet - *.do - - - - 30 - - - /index.jsp - /index.htm - - - diff --git a/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/AppTest.java b/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/AppTest.java new file mode 100644 index 0000000000000000000000000000000000000000..d59e2aebca2a5afc7bc558997b3ebbaf9b72823e --- /dev/null +++ b/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/AppTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) [2022] smartboot [zhengjunweimail@163.com] + * + * 企业用户未经smartboot组织特别许可,需遵循AGPL-3.0开源协议合理合法使用本项目。 + * + * Enterprise users are required to use this project reasonably + * and legally in accordance with the AGPL-3.0 open source agreement + * without special permission from the smartboot organization. + */ + +package org.smartboot.servlet.testsuite.test; + +import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; + +/** + * Unit test for simple App. + */ +public class AppTest extends com.sun.ts.tests.servlet.api.jakarta_servlet_http.sessioncookieconfig.URLClient // com.sun.ts.tests.servlet.api.jakarta_servlet_http.sessioncookieconfig.URLClient // com.sun.ts.tests.servlet.spec.security.annotations.Client +{ + + @Test + public void foo() throws Exception { + // + } + + @Test + public void bar() throws Exception { + LoggerFactory loggerFactory; + } + +} diff --git a/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/BastTest.java b/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/BastTest.java deleted file mode 100644 index fd3c53c7945f34bf1f4289855031a51c516aaa9f..0000000000000000000000000000000000000000 --- a/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/BastTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2017-2021, org.smartboot. All rights reserved. - * project name: smart-servlet - * file name: BastTest.java - * Date: 2021-05-14 - * Author: sandao (zhengjunweimail@163.com) - * - */ -package org.smartboot.servlet.testsuite.test; - -import com.alibaba.fastjson.JSONObject; -import org.junit.Assert; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smartboot.http.client.HttpClient; -import org.smartboot.http.client.HttpResponse; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -/** - * @author 三刀(zhengjunweimail@163.com) - * @version V1.0 , 2021/5/14 - */ -public class BastTest { - private static final Logger LOGGER = LoggerFactory.getLogger(ServletTest.class); - private static final String CONTENT_PATH = "/demo"; - - protected HttpClient getSmartClient() { - HttpClient httpClient = new HttpClient("127.0.0.1", 8081); - httpClient.connect(); - return httpClient; - } - - protected HttpClient getTomcatClient() { - HttpClient httpClient = new HttpClient("127.0.0.1", 8082); - httpClient.connect(); - return httpClient; - } - - protected void checkPath(String path, HttpClient smartClient, HttpClient tomcatClient) { - Future smartFuture = smartClient.get(CONTENT_PATH + path).onSuccess(resp -> { - LOGGER.info("smart-servlet response: {}", resp.body()); - }).send(); - Future tomcatFuture = tomcatClient.get(CONTENT_PATH + path).onSuccess(resp -> { - LOGGER.info("tomcat response: {}", resp.body()); - }).send(); - try { - checkResponse(smartFuture.get(), tomcatFuture.get()); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - } - - protected void checkResponse(HttpResponse smartResponse, HttpResponse tomcatResponse) { - JSONObject smartJson = JSONObject.parseObject(smartResponse.body()); - JSONObject tomcatJson = JSONObject.parseObject(tomcatResponse.body()); - Assert.assertEquals("key 数量不一致", smartJson.size(), tomcatJson.size()); - for (String key : smartJson.keySet()) { - Assert.assertEquals("key: " + key + " 匹配失败", smartJson.getString(key), tomcatJson.getString(key)); - } - } -} diff --git a/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/ServletTest.java b/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/ServletTest.java deleted file mode 100644 index 98023ddb6a709eec56ece12ac08d0b9f6b355d14..0000000000000000000000000000000000000000 --- a/testsuite/src/test/java/org/smartboot/servlet/testsuite/test/ServletTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2017-2021, org.smartboot. All rights reserved. - * project name: smart-servlet - * file name: ServletTest.java - * Date: 2021-05-14 - * Author: sandao (zhengjunweimail@163.com) - * - */ - -package org.smartboot.servlet.testsuite.test; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.smartboot.http.client.HttpClient; - -/** - * @author 三刀(zhengjunweimail@163.com) - * @version V1.0 , 2021/5/14 - */ -public class ServletTest extends BastTest { - private static final Logger LOGGER = LoggerFactory.getLogger(ServletTest.class); - private HttpClient smartClient; - private HttpClient tomcatClient; - - @Before - public void init() { - smartClient = getSmartClient(); - tomcatClient = getTomcatClient(); - } - - /** - * 精确匹配 - */ - @Test - public void test1() { - checkPath("/demo", smartClient, tomcatClient); - } - - /** - * 前缀匹配 - */ - @Test - public void test2() { - checkPath("/pathMatch", smartClient, tomcatClient); - } - - /** - * 前缀匹配 - */ - @Test - public void test3() { - checkPath("/pathMatch/1", smartClient, tomcatClient); - } - - /** - * 前缀匹配,包含query - */ - @Test - public void test4() { - checkPath("/pathMatch/1?abc=c&bdc=4", smartClient, tomcatClient); - } - - - /** - * 扩展名匹配,包含query - */ - @Test - public void test6() { - checkPath("/pathMatch/abc.do?abc=c&bdc=4", smartClient, tomcatClient); - } - - /** - * 扩展名匹配,包含query - */ - @Test - public void test7() { - checkPath("/abc/abc.do?abc=c&bdc=4", smartClient, tomcatClient); - } - - /** - * 扩展名匹配,包含query - */ - @Test - public void test8() { - checkPath("/adb/abc/abc.do?abc=c&bdc=4", smartClient, tomcatClient); - } - - @After - public void destroy() { - smartClient.close(); - tomcatClient.close(); - } -} diff --git a/testsuite/src/test/resources/arquillian.xml b/testsuite/src/test/resources/arquillian.xml new file mode 100644 index 0000000000000000000000000000000000000000..0514924d66d57e03b71562587fb8144b4adada6c --- /dev/null +++ b/testsuite/src/test/resources/arquillian.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + 8080 + RFC2965 + RFC2965 + src/test/resources/default.properties + true + + text/html iso-8859-1 + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/testsuite/src/test/resources/default.properties b/testsuite/src/test/resources/default.properties new file mode 100644 index 0000000000000000000000000000000000000000..1a441165817fef4f81a39470e19dc7ec87919da7 --- /dev/null +++ b/testsuite/src/test/resources/default.properties @@ -0,0 +1,27 @@ +# +# This file defines users passwords and roles for a HashUserRealm +# +# The format is +# : [, ...] +# +# Passwords may be clear text, obfuscated or checksummed. The class +# org.eclipse.util.Password should be used to generate obfuscated +# passwords or password checksums +# +# If DIGEST Authentication is used, the password must be in a recoverable +# format, either plain text or OBF:. +# +jetty: MD5:164c88b302622e17050af52c89945d44,user +admin: CRYPT:adpexzg3FUZAk,server-administrator,content-administrator,admin,user +other: OBF:1xmk1w261u9r1w1c1xmq,user +plain: plain,user +user: password,user + +# This entry is for digest auth. The credential is a MD5 hash of username:realmname:password +digest: MD5:6e120743ad67abfbc385bc2bb754e297,user + +j2ee: j2ee,Administrator,Employee +javajoe: javajoe,VP,Manager + +# CN=CTS, OU=Java Software, O=Sun Microsystems Inc., L=Burlington, ST=MA, C=US +CN\=CTS,\ OU\=Java\ Software,\ O\=Sun\ Microsystems\ Inc.,\ L\=Burlington,\ ST\=MA,\ C\=US=,,Administrator diff --git a/testsuite/src/test/resources/servlet_spec_fragment_web/webdefault.xml b/testsuite/src/test/resources/servlet_spec_fragment_web/webdefault.xml new file mode 100644 index 0000000000000000000000000000000000000000..e653db53398e3797b37d2af2bdc80f4484c122bc --- /dev/null +++ b/testsuite/src/test/resources/servlet_spec_fragment_web/webdefault.xml @@ -0,0 +1,399 @@ + + + + + + + + + + Default web.xml file. + This file is applied to a Web application before its own WEB_INF/web.xml file + + + + + + + + org.eclipse.jetty.servlet.listener.ELContextCleaner + + + + + + + + org.eclipse.jetty.servlet.listener.IntrospectorCleaner + + + + + + + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + acceptRanges + true + + + dirAllowed + true + + + welcomeServlets + exact + + + redirectWelcome + false + + + maxCacheSize + 256000000 + + + maxCachedFileSize + 200000000 + + + maxCachedFiles + 2048 + + + etags + false + + + useFileMappedBuffer + true + + 0 + + + + default + / + + + + + + + + + + + + + + + jsp + org.eclipse.jetty.jsp.JettyJspServlet + + xpoweredBy + false + + + compilerTargetVM + 1.8 + + + compilerSourceVM + 1.8 + + 0 + + + + jsp + *.jsp + *.jspf + *.jspx + *.xsp + *.JSP + *.JSPF + *.JSPX + *.XSP + + + + + + + + 30 + + + + + + + + index.html + index.htm + index.jsp + + + + + + + + ar + ISO-8859-6 + + + be + ISO-8859-5 + + + bg + ISO-8859-5 + + + ca + ISO-8859-1 + + + cs + ISO-8859-2 + + + da + ISO-8859-1 + + + de + ISO-8859-1 + + + el + ISO-8859-7 + + + en + ISO-8859-1 + + + es + ISO-8859-1 + + + et + ISO-8859-1 + + + fi + ISO-8859-1 + + + fr + ISO-8859-1 + + + hr + ISO-8859-2 + + + hu + ISO-8859-2 + + + is + ISO-8859-1 + + + it + ISO-8859-1 + + + iw + ISO-8859-8 + + + ja + Shift_JIS + + + ko + EUC-KR + + + lt + ISO-8859-2 + + + lv + ISO-8859-2 + + + mk + ISO-8859-5 + + + nl + ISO-8859-1 + + + no + ISO-8859-1 + + + pl + ISO-8859-2 + + + pt + ISO-8859-1 + + + ro + ISO-8859-2 + + + ru + ISO-8859-5 + + + sh + ISO-8859-5 + + + sk + ISO-8859-2 + + + sl + ISO-8859-2 + + + sq + ISO-8859-2 + + + sr + ISO-8859-5 + + + sv + ISO-8859-1 + + + tr + ISO-8859-9 + + + uk + ISO-8859-5 + + + zh + GB2312 + + + zh_TW + Big5 + + + + + + + + + Disable TRACE + / + TRACE + + + + + + Enable everything but TRACE + / + TRACE + + + + + diff --git a/testsuite/src/test/resources/simplelogger.properties b/testsuite/src/test/resources/simplelogger.properties new file mode 100644 index 0000000000000000000000000000000000000000..c42caf080b586f0d20afc650927bc85f6661b3a4 --- /dev/null +++ b/testsuite/src/test/resources/simplelogger.properties @@ -0,0 +1,8 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +#org.slf4j.simpleLogger.log.org.eclipse.jetty=debug +#org.slf4j.simpleLogger.log.org.eclipse.jetty.security=info +#org.slf4j.simpleLogger.log.org.eclipse.jetty.annotations=debug +org.slf4j.simpleLogger.log.org.eclipse.jetty.io=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.server=info +org.slf4j.simpleLogger.log.org.eclipse.jetty.util=info +