第一版教学,作业扩展

This commit is contained in:
maxf
2025-07-11 15:36:02 +08:00
parent 61a5493cc0
commit 5c87319677
39 changed files with 1760 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
package top.yexuejc.demo;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import top.yexuejc.demo.core.WebServer;
/**
* @author maxiaofeng
* @date 2025/7/11 13:56
*/
public class AppConfig {
private static Properties properties = new Properties();
private static boolean loaded = false;
public static Properties getProperties() {
if (!loaded) {
loaded = true;
try {
loadDefaultConfig();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return properties;
}
/**
* 配置加载,同key不覆蓋
*/
public static void loadAfter(Properties p) {
p.putAll(properties);
properties = p;
}
public static void put(String key, String value) {
properties.put(key, value);
}
public static synchronized void loadDefaultConfig() throws Exception {
loadConfig("classpath:application.properties");
}
public static Properties loadConfig(String path) throws Exception {
if (path.startsWith("classpath:")) {
// classpath:application.properties
try (InputStream inputStream = WebServer.class.getClassLoader().getResourceAsStream(path.substring(10))) {
Properties properties = new Properties();
properties.load(inputStream);
AppConfig.loadAfter(properties);
} catch (IOException e) {
throw new Exception(e);
}
} else {
// /home/app/application.properties
try (InputStream inputStream = new FileInputStream(path)) {
Properties properties = new Properties();
properties.load(inputStream);
AppConfig.loadAfter(properties);
} catch (IOException e) {
throw new Exception(e);
}
}
return AppConfig.properties;
}
}

View File

@@ -0,0 +1,58 @@
package top.yexuejc.demo;
import top.yexuejc.demo.core.ControllerSupplier;
import top.yexuejc.demo.core.WebServer;
/**
* web服务器入口
*
* @author maxiaofeng
* @date 2025/6/19 11:11
*/
public class WebServerApplication {
public static void main(String[] args) {
System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS [%4$s] %2$s - %5$s%6$s%n");
argsProcess(args);
ControllerSupplier.builder(WebServerApplication.class);
new WebServer().start();
}
/**
* 处理参数
* @param args
*/
private static void argsProcess(String[] args) {
if (args != null) {
boolean argsCheck = true;
for (String arg : args) {
String[] split = arg.split("=");
if (split.length == 2) {
String value = split[1];
switch (split[0]) {
case "--http.port":
AppConfig.put("http.port", value);
break;
case "--https.port":
AppConfig.put("https.port", value);
break;
case "--property":
try {
AppConfig.loadConfig(value);
} catch (Exception e) {
System.out.println("参数错误:" + arg);
argsCheck = false;
}
break;
default:
System.out.println("参数错误:" + arg);
argsCheck = false;
break;
}
}
}
if (!argsCheck) {
System.exit(0);
}
}
}
}

View File

@@ -0,0 +1,18 @@
package top.yexuejc.demo.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author maxiaofeng
* @date 2025/7/11 14:36
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface GetMapping {
String[] value() default {};
}

View File

@@ -0,0 +1,17 @@
package top.yexuejc.demo.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author maxiaofeng
* @date 2025/7/11 14:35
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestController {
}

View File

@@ -0,0 +1,168 @@
package top.yexuejc.demo.core;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import top.yexuejc.demo.annotation.GetMapping;
import top.yexuejc.demo.annotation.RestController;
/**
* controller类提供者
*
* @author maxiaofeng
* @date 2025/7/11 14:40
*/
public class ControllerSupplier {
private final String scanPackage;
private List<Class<?>> controllerClasses = new ArrayList<>();
/** @GetMapping 路径映射 */
private Map<String, CtrlBean> getMapping = new ConcurrentHashMap<>();
private static volatile ControllerSupplier instance;
private ControllerSupplier(String scanPackage) {
this.scanPackage = scanPackage;
}
public static ControllerSupplier getInstance(String scanPackage) {
if (instance == null) {
synchronized (ControllerSupplier.class) {
if (instance == null) {
instance = new ControllerSupplier(scanPackage);
}
}
}
return instance;
}
public static void builder(Class<?> applicationClass) {
// 得到类的包名
String packageName = applicationClass.getPackage().getName();
ControllerSupplier controllerSupplier = ControllerSupplier.getInstance(packageName);
controllerSupplier.load();
}
/**
* 扫描包下的@RestController和@Controller注解的类
*/
public void load() {
try {
// 获取包路径对应的 URL 资源
String packagePath = scanPackage.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(packagePath);
List<Class<?>> classes = new ArrayList<>();
while (resources.hasMoreElements()) {
File file = new File(resources.nextElement().getFile());
if (file.isDirectory()) {
// 遍历目录下所有 .class 文件并加载类
List<Class<?>> foundClasses = getClassesFromDirectory(file, scanPackage);
classes.addAll(foundClasses);
}
}
// 筛选出带有 @RestController 的类
controllerClasses = classes.stream().filter(clazz -> clazz.isAnnotationPresent(RestController.class)).toList();
controllerClasses.forEach(clazz -> {
System.out.println("controller: " + clazz.getName());
// 实例化类
Object ctrl;
try {
ctrl = clazz.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
// 找到类下的@GetMapping 方法
Arrays.stream(clazz.getMethods()).filter(method -> method.isAnnotationPresent(GetMapping.class)).forEach(method -> {
GetMapping getMappingAnnotation = method.getAnnotation(GetMapping.class);
String[] uris = getMappingAnnotation.value();
for (String uri : uris) {
System.out.println("uri: " + uri);
getMapping.put(uri, new CtrlBean(ctrl, method));
}
});
});
} catch (Exception e) {
e.printStackTrace();
}
}
public static class CtrlBean {
public final Object ctrl;
public final Method method;
public CtrlBean(Object ctrl, Method method) {
this.ctrl = ctrl;
this.method = method;
}
}
/**
* 从给定目录扫描并加载所有类
*/
private List<Class<?>> getClassesFromDirectory(File directory, String packageName) throws ClassNotFoundException {
List<Class<?>> classes = new ArrayList<>();
if (!directory.exists()) {
return classes;
}
File[] files = directory.listFiles();
if (files == null) {
return classes;
}
for (File file : files) {
if (file.isDirectory()) {
classes.addAll(getClassesFromDirectory(file, STR."\{packageName}.\{file.getName()}"));
} else if (file.getName().endsWith(".class")) {
String className = STR."\{packageName}.\{file.getName().substring(0, file.getName().length() - 6)}";
Class<?> clazz = Class.forName(className);
classes.add(clazz);
}
}
return classes;
}
/**
* 处理请求
*
* @param request
* @return
*/
public static Response invoke(Request request) {
CtrlBean ctrlBean = ControllerSupplier.getInstance(null).getMapping.get(request.getPath());
if (ctrlBean != null) {
try {
// 判断method是否有参数
Object result;
if (ctrlBean.method.getParameterCount() == 0) {
result = ctrlBean.method.invoke(ctrlBean.ctrl);
} else {
result = ctrlBean.method.invoke(ctrlBean.ctrl, request.getParams());
}
return new Response(200, "OK", "text/html;charset=utf-8", result.toString().getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
return new Response(500, "Internal Server Error", "text/html;charset=utf-8", "Internal Server Error".getBytes(StandardCharsets.UTF_8));
}
}
return null;
}
}

View File

@@ -0,0 +1,36 @@
package top.yexuejc.demo.core;
/**
* 请求对象类
* @author maxiaofeng
* @date 2025/6/19 11:37
*/
public class Request {
private final String method;
private final String path;
private final String body;
private final String params;
public Request(String method, String path, String body, String params) {
this.method = method;
this.path = path;
this.body = body;
this.params = params;
}
public String getMethod() {
return method;
}
public String getPath() {
return path;
}
public String getBody() {
return body;
}
public String getParams() {
return params;
}
}

View File

@@ -0,0 +1,234 @@
package top.yexuejc.demo.core;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import java.util.logging.Logger;
import javax.net.ssl.SSLHandshakeException;
/**
* 请求处理
*
* @author maxiaofeng
* @date 2025/6/19 11:29
*/
public class RequestHandler implements Runnable {
Logger logger = Logger.getLogger(RequestHandler.class.getName());
private final String OS_NAME = System.getProperty("os.name");
private final List<String> STATIC_RESOURCES = List.of("css", "js", "img", "fonts", "favicon.ico");
private final Socket clientSocket;
private final String staticRoot;
private final String webRoot;
public RequestHandler(Socket socket, String staticRoot, String webRoot) {
this.clientSocket = socket;
this.staticRoot = staticRoot;
this.webRoot = webRoot;
}
@Override
public void run() {
try (InputStream input = clientSocket.getInputStream(); OutputStream output = clientSocket.getOutputStream()) {
// 解析请求
Request request = parseRequest(input);
Response response = ControllerSupplier.invoke(request);
if (response == null) {
// 处理请求并生成响应(静态画面)
response = handleRequestPage(request);
}
// 画面渲染(动态画面)
pageLive(request, response);
// 发送响应
sendResponse(output, response);
} catch (SSLHandshakeException e) {
logger.warning("证书不受信任!请导入证书或使用受信任的证书。");
} catch (Exception e) {
logger.warning("Error handling request: " + e.getMessage());
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
logger.warning("Error closing client socket: " + e.getMessage());
e.printStackTrace();
}
}
}
private void pageLive(Request request, Response response) {
if (!request.getPath().endsWith("html") && !request.getPath().equals("/")) {
return;
}
byte[] originalBytes = response.getContent();
// 模拟模板引擎
// 1. 获取原始的字符串
String originalString = new String(originalBytes, StandardCharsets.UTF_8);
// 2. 替换字符串
String params = request.getParams();
if (params == null) {
params = "";
} else {
String[] split = params.split("&");
for (String s : split) {
String[] split1 = s.split("=");
if (split1.length == 2) {
originalString = originalString.replace(STR."${\{split1[0]}}", split1[1]);
}
}
}
// 3. 将替换后的 String 转回 byte[]
response.setContent(originalString.getBytes(StandardCharsets.UTF_8));
}
private Request parseRequest(InputStream input) throws IOException, URISyntaxException {
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
String requestLine = reader.readLine();
if (requestLine == null) {
throw new IOException("Empty request");
}
// GET /index.html?a=1 HTTP/1.1
// Host: 127.0.0.1:8080
// User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;
// v=b3;q=0.7
// POST /getUser HTTP/1.1
// Content-Type: application/json
// User-Agent: PostmanRuntime/7.44.0
// Accept: */*
// Host: 127.0.0.1:8080
// Accept-Encoding: gzip, deflate, br
// Connection: keep-alive
// Content-Length: 10
logger.info("=====================================================");
logger.info(requestLine);
String[] parts = requestLine.split(" ");
if (parts.length != 3) {
throw new IOException("Invalid request line: " + requestLine);
}
String method = parts[0]; // GET
String path = parts[1]; // /index.html
URI url = new URI(path);
path = url.getPath();
String params = url.getQuery();
// 读取请求头
String headerLine;
int contentLength = 0; // 输入流中字节长度
while ((headerLine = reader.readLine()) != null && !headerLine.isEmpty()) {
logger.info(headerLine);
if (method.equals("POST") && headerLine.startsWith("Content-Length:")) {
contentLength = Integer.parseInt(headerLine.split(":")[1].trim());
}
}
// 读取POST请求体
StringBuilder requestBody = new StringBuilder();
if (method.equals("POST") && contentLength > 0) {
char[] buffer = new char[contentLength];
reader.read(buffer, 0, contentLength);
requestBody.append(buffer);
logger.info("Body: " + requestBody);
}
logger.info("Params: " + params);
logger.info("=====================================================");
return new Request(method, path, requestBody.toString(), params);
}
private Response handleRequestPage(Request request) {
String path = request.getPath();
// 默认主页
if (path.equals("/")) {
path = "/index.html";
}
path = getAbsolutePath(path);
// 获取classPath中的文件路径
String classPath = Objects.requireNonNull(getClass().getClassLoader().getResource("")).getPath();
if (OS_NAME.startsWith("Windows")) {
classPath = classPath.substring(1);
}
Path filePath = Paths.get(classPath, path);
// 安全检查,防止目录遍历攻击
if (!filePath.startsWith(Paths.get(classPath, webRoot)) && !filePath.startsWith(Paths.get(classPath, staticRoot))) {
return new Response(403, "Forbidden", "text/plain;charset=utf-8", "Access denied".getBytes());
}
// 检查文件是否存在且是普通文件
if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
try {
byte[] content = Files.readString(filePath, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
String contentType = determineContentType(filePath);
return new Response(200, "OK", contentType, content);
} catch (IOException e) {
return new Response(500, "Internal Server Error", "text/plain;charset=utf-8", "Error reading file".getBytes());
}
} else {
return new Response(404, "Not Found", "text/plain;charset=utf-8", "Page not found".getBytes());
}
}
private String getAbsolutePath(String path) {
if (path.startsWith("/") && STATIC_RESOURCES.stream().anyMatch(path::endsWith)) {
return staticRoot + path;
}
return webRoot + path;
}
private String determineContentType(Path filePath) {
String fileName = filePath.getFileName().toString();
if (fileName.endsWith(".html")) {
return "text/html;charset=utf-8";
} else if (fileName.endsWith(".css")) {
return "text/css;charset=utf-8";
} else if (fileName.endsWith(".js")) {
return "application/javascript;charset=utf-8";
} else if (fileName.endsWith(".png")) {
return "image/png";
} else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
return "image/jpeg";
} else {
return "application/octet-stream";
}
}
private void sendResponse(OutputStream output, Response response) throws IOException {
String statusLine = STR."""
HTTP/1.1 \{response.getStatusCode()} \{response.getStatusMessage()}\r
""";
String headers = STR."""
Content-Type: \{response.getContentType()}\r
Content-Length: \{response.getContentLength()}\r
\r
""";
output.write(statusLine.getBytes());
output.write(headers.getBytes());
output.write(response.getContent());
output.flush();
}
}

View File

@@ -0,0 +1,45 @@
package top.yexuejc.demo.core;
/**
* 响应对象类
* @author maxiaofeng
* @date 2025/6/19 11:37
*/
public class Response {
private final int statusCode;
private final String statusMessage;
private final String contentType;
private byte[] content;
public Response(int statusCode, String statusMessage, String contentType, byte[] content) {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.contentType = contentType;
this.content = content;
}
public int getStatusCode() {
return statusCode;
}
public String getStatusMessage() {
return statusMessage;
}
public String getContentType() {
return contentType;
}
public byte[] getContent() {
return content;
}
public int getContentLength() {
return content.length;
}
public Response setContent(byte[] content) {
this.content = content;
return this;
}
}

View File

@@ -0,0 +1,165 @@
package top.yexuejc.demo.core;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.util.Properties;
import java.util.logging.Logger;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import top.yexuejc.demo.AppConfig;
/**
* web服务核心
*
* @author maxiaofeng
* @date 2025/6/19 11:13
*/
public class WebServer {
Logger logger = Logger.getLogger(WebServer.class.getName());
/** 端口 */
private int httpPort = 80;
private int httpsPort = 443;
/**
* 证书格式:
* 1. JKS
* 2. PKCS12
*/
private String httpsSslKeyStore = "PKCS12";
private String httpsSslKeyStoreFile;
private String httpsSslKeyStorePassword;
/** 页面资源文件 */
private String webRoot = "web";
/** 静态资源文件 */
private String staticRoot = "static";
private ServerSocket serverSocket;
private SSLServerSocket httpsServerSocket;
private boolean isRunning;
public WebServer() {
Properties config = AppConfig.getProperties();
String s = config.getProperty("http.port");
httpPort = s == null ? 80 : Integer.parseInt(s);
s = config.getProperty("https.port");
httpsPort = s == null ? 443 : Integer.parseInt(s);
s = config.getProperty("https.ssl.type");
httpsSslKeyStore = s == null ? httpsSslKeyStore : s;
s = config.getProperty("https.ssl.key-store");
httpsSslKeyStoreFile = s == null ? httpsSslKeyStoreFile : s;
s = config.getProperty("https.ssl.key-store-password");
httpsSslKeyStorePassword = s == null ? httpsSslKeyStorePassword : s;
}
public WebServer(int httpPort, int httpsPort, String staticRoot, String webRoot) {
this.httpPort = httpPort;
this.httpsPort = httpsPort;
this.staticRoot = staticRoot;
this.webRoot = webRoot;
}
/**
* 启动服务
*/
public void start() {
new Thread(this::startHttp).start();
if (httpsSslKeyStoreFile != null && !httpsSslKeyStoreFile.isEmpty()) {
new Thread(this::startHttps).start();
}
}
public void startHttp() {
try {
serverSocket = new ServerSocket(httpPort);
isRunning = true;
logger.info("启动Web服务 : http://127.0.0.1:" + httpPort);
// 阻塞式监听服务是否正常
while (isRunning) {
try {
Socket clientSocket = serverSocket.accept();
// 新建一个线程处理请求
new RequestHandler(clientSocket, staticRoot, webRoot).run();
} catch (IOException e) {
if (isRunning) {
logger.severe("创建连接异常。");
e.printStackTrace();
}
}
}
} catch (Throwable r) {
logger.severe("启动Web服务异常。");
r.printStackTrace();
} finally {
stop();
}
}
public void startHttps() {
try {
// 1. 加载 PKCS12 密钥库
KeyStore keyStore = KeyStore.getInstance(httpsSslKeyStore);
InputStream inputStream;
if (httpsSslKeyStoreFile.startsWith("classpath:")) {
inputStream = this.getClass().getClassLoader().getResourceAsStream(httpsSslKeyStoreFile.substring(10));
} else {
inputStream = new FileInputStream(httpsSslKeyStoreFile);
}
keyStore.load(inputStream, httpsSslKeyStorePassword.toCharArray());
// 2. 初始化 KeyManagerFactory
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, httpsSslKeyStorePassword.toCharArray());
// 3. 初始化 SSLContextTLS 1.2 或 1.3
SSLContext sslContext = SSLContext.getInstance("TLSv1.3"); // 或 "TLS"
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
// 4. 创建 SSLServerSocket
SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory();
SSLServerSocket httpsServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(httpsPort);
httpsServerSocket.setEnabledProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
logger.info("启动Web服务 : https://127.0.0.1:" + httpsPort);
isRunning = true;
while (isRunning) {
// 新建一个线程处理请求
new RequestHandler(httpsServerSocket.accept(), staticRoot, webRoot).run();
}
} catch (Exception e) {
if (isRunning) {
logger.severe("创建连接异常。");
e.printStackTrace();
}
} finally {
stop();
}
}
/**
* 停止服务
*/
public void stop() {
isRunning = false;
try {
if (serverSocket != null) {
serverSocket.close();
}
} catch (IOException e) {
logger.info("停止Web服务异常: " + e.getMessage());
}
try {
if (httpsServerSocket != null) {
httpsServerSocket.close();
}
} catch (IOException e) {
logger.info("停止Web服务异常: " + e.getMessage());
}
System.out.println("Web服务停止.");
}
}

View File

@@ -0,0 +1,22 @@
package top.yexuejc.demo.web;
import top.yexuejc.demo.annotation.GetMapping;
import top.yexuejc.demo.annotation.RestController;
/**
* @author maxiaofeng
* @date 2025/7/11 14:33
*/
@RestController
public class IndexCtrl {
@GetMapping("/test")
public String index() {
return "hello world";
}
@GetMapping("/test2")
public String index(String params) {
return STR."你好 \{params}";
}
}

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIEEBU9hTANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJD
TjERMA8GA1UEAwwISW50YXNlY3QxEDAOBgNVBAgMB1NpQ2h1YW4xEDAOBgNVBAcM
B0NoZW5nRHUxETAPBgNVBAoMCEludGFzZWN0MREwDwYDVQQLDAhJbnRhc2VjdDAe
Fw0yNTA3MTEwMTM5MDhaFw0yNzA3MTEwMTM5MDhaMGoxCzAJBgNVBAYTAkNOMREw
DwYDVQQDDAhJbnRhc2VjdDEQMA4GA1UECAwHU2lDaHVhbjEQMA4GA1UEBwwHQ2hl
bmdEdTERMA8GA1UECgwISW50YXNlY3QxETAPBgNVBAsMCEludGFzZWN0MIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Ce46Pj6hIeCNv/g+c4WaKJ5jURo
fk0mLXhTaLa/QdsK9OVGUYWPeYlQtljZZ7y5o3DNHnYJ7bSeAQUE/NY786Owz7jN
JER9ppqTg/2lpklgKXKEC+4UsSWuPCmBTQoXaU5SGX7l3esrR1n+vIUocOOyHlWv
KzHCwiTJxEYbShC+kdgvB5ozWSTp5qF8j5sACSKNmdckSaIfiW14b3QUsNBt7uMv
cS4VsrzxPBEcD2qWCkSrw6Q4Tvy7yhU21taisUO9diA6MfMZX6988U4lmW97R31m
2iQwxVr1m6tQnkvJTHwR76xqKxW8lDKVBt42e42KEcFrTPNseDGWfh/WFwIDAQAB
o1AwTjAdBgNVHQ4EFgQU526ItlTmddeCAhwL8Riz6Cv3Ze4wHwYDVR0jBBgwFoAU
526ItlTmddeCAhwL8Riz6Cv3Ze4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsF
AAOCAQEAaYr52M5ZNUB0MQFHqGQ5FvIDaBZJY9ntYC+iasW84EQcPzZy+XCeMcqd
sv9RA1/UrNnmPM62AgM0TNUNHg/edEeVBf/IrqGdPNPEYKiB4lELWuzyVvjEkyEK
0azf+GzOYEYsbqU98Jh3tTb2d5fqmN59otxqe7b9aA5BogL/n9AmAOw+1DgJ4l4g
Qj5S+tA5jR4DpQthvrrzy0/xc37gIvzS7em7y6C2K6lDE0CCs9OuBZoAniaa+6/I
86Xi+XtJFrZ1bFfyOwDp7TSuNALeEsJKggfASMbOG0NUGcgl5q/kMcp5v8e6RNUw
oLBEvTic573dAWwtuCjOTW0QI2vG7w==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,5 @@
http.port=80
https.port=443
https.ssl.type=PKCS12
https.ssl.key-store=classpath:ssl/keystore_3rd.p12
https.ssl.key-store-password=123456

Binary file not shown.

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIEEBU9hTANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJD
TjERMA8GA1UEAwwISW50YXNlY3QxEDAOBgNVBAgMB1NpQ2h1YW4xEDAOBgNVBAcM
B0NoZW5nRHUxETAPBgNVBAoMCEludGFzZWN0MREwDwYDVQQLDAhJbnRhc2VjdDAe
Fw0yNTA3MTEwMTM5MDhaFw0yNzA3MTEwMTM5MDhaMGoxCzAJBgNVBAYTAkNOMREw
DwYDVQQDDAhJbnRhc2VjdDEQMA4GA1UECAwHU2lDaHVhbjEQMA4GA1UEBwwHQ2hl
bmdEdTERMA8GA1UECgwISW50YXNlY3QxETAPBgNVBAsMCEludGFzZWN0MIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3Ce46Pj6hIeCNv/g+c4WaKJ5jURo
fk0mLXhTaLa/QdsK9OVGUYWPeYlQtljZZ7y5o3DNHnYJ7bSeAQUE/NY786Owz7jN
JER9ppqTg/2lpklgKXKEC+4UsSWuPCmBTQoXaU5SGX7l3esrR1n+vIUocOOyHlWv
KzHCwiTJxEYbShC+kdgvB5ozWSTp5qF8j5sACSKNmdckSaIfiW14b3QUsNBt7uMv
cS4VsrzxPBEcD2qWCkSrw6Q4Tvy7yhU21taisUO9diA6MfMZX6988U4lmW97R31m
2iQwxVr1m6tQnkvJTHwR76xqKxW8lDKVBt42e42KEcFrTPNseDGWfh/WFwIDAQAB
o1AwTjAdBgNVHQ4EFgQU526ItlTmddeCAhwL8Riz6Cv3Ze4wHwYDVR0jBBgwFoAU
526ItlTmddeCAhwL8Riz6Cv3Ze4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsF
AAOCAQEAaYr52M5ZNUB0MQFHqGQ5FvIDaBZJY9ntYC+iasW84EQcPzZy+XCeMcqd
sv9RA1/UrNnmPM62AgM0TNUNHg/edEeVBf/IrqGdPNPEYKiB4lELWuzyVvjEkyEK
0azf+GzOYEYsbqU98Jh3tTb2d5fqmN59otxqe7b9aA5BogL/n9AmAOw+1DgJ4l4g
Qj5S+tA5jR4DpQthvrrzy0/xc37gIvzS7em7y6C2K6lDE0CCs9OuBZoAniaa+6/I
86Xi+XtJFrZ1bFfyOwDp7TSuNALeEsJKggfASMbOG0NUGcgl5q/kMcp5v8e6RNUw
oLBEvTic573dAWwtuCjOTW0QI2vG7w==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICrzCCAZcCAQAwajELMAkGA1UEBhMCQ04xETAPBgNVBAMMCEludGFzZWN0MRAw
DgYDVQQIDAdTaUNodWFuMRAwDgYDVQQHDAdDaGVuZ0R1MREwDwYDVQQKDAhJbnRh
c2VjdDERMA8GA1UECwwISW50YXNlY3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDcJ7jo+PqEh4I2/+D5zhZoonmNRGh+TSYteFNotr9B2wr05UZRhY95
iVC2WNlnvLmjcM0edgnttJ4BBQT81jvzo7DPuM0kRH2mmpOD/aWmSWApcoQL7hSx
Ja48KYFNChdpTlIZfuXd6ytHWf68hShw47IeVa8rMcLCJMnERhtKEL6R2C8HmjNZ
JOnmoXyPmwAJIo2Z1yRJoh+JbXhvdBSw0G3u4y9xLhWyvPE8ERwPapYKRKvDpDhO
/LvKFTbW1qKxQ712IDox8xlfr3zxTiWZb3tHfWbaJDDFWvWbq1CeS8lMfBHvrGor
FbyUMpUG3jZ7jYoRwWtM82x4MZZ+H9YXAgMBAAGgADANBgkqhkiG9w0BAQsFAAOC
AQEAhHafpZDX/Ivl+27k+/4/npdAW8VSbUS++txJvB2u+r4WdwLZei5LSQDKzdfY
BAfcyhHxNTUV90OB68jqYoi2RiivtaNzQVGaFJ/XrkfZqQq7UNramHbdisV/m5hg
oFYoei52SDIXLv1BzZhBMFr2TtLTZMt5xXfaQjInInjcZJs8ngtwQL/DyPlrPNzS
TOZrl4GHMIIyUejqG2TibKiugkFoZ2DwHyZggIgeFqfU7PNc2KQJ/BtYs4N0IpvF
k0Yy+2Mi6lWYm9Nn7pHgo4/+8ZWrD/z5Ffsmgsrwv6S4iB/z3oVqJdEYnryHqy78
WytygTsyK1SBLBDEdHgXbazfQw==
-----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcJ7jo+PqEh4I2
/+D5zhZoonmNRGh+TSYteFNotr9B2wr05UZRhY95iVC2WNlnvLmjcM0edgnttJ4B
BQT81jvzo7DPuM0kRH2mmpOD/aWmSWApcoQL7hSxJa48KYFNChdpTlIZfuXd6ytH
Wf68hShw47IeVa8rMcLCJMnERhtKEL6R2C8HmjNZJOnmoXyPmwAJIo2Z1yRJoh+J
bXhvdBSw0G3u4y9xLhWyvPE8ERwPapYKRKvDpDhO/LvKFTbW1qKxQ712IDox8xlf
r3zxTiWZb3tHfWbaJDDFWvWbq1CeS8lMfBHvrGorFbyUMpUG3jZ7jYoRwWtM82x4
MZZ+H9YXAgMBAAECggEATkD0UiNF8Nu15lTXpBOkFXdDG3qoZdSIcHsnsr3ah88T
Ou9QKmP+FqY/gUFdrakAl17eGii86LhdvWEKX9DKqJSToZI/oNeTjie9rZn4Sn4k
ZzckRpVO15TcNNhP9JFUtwK23gckL9iKnqcXi+0M7euRgYTVadYbMyUebty4kH9w
YhGVDdoyBqsi4MpVU1EiszRfMrvtWYywpjrCwdcwsliGUXIOdz3AWabCpmn2N0Lc
KHgdqPuDG57wAS7Wr/4UdfghIf0ywAhpL0A2F514dc0cDWsC5C33SSO77S3Gx4Wl
Xz9lqkKtWh+30DNKShkb+YG030Y/p0Msxn9PlMzZMQKBgQDxe78cNqrWCMgj35yD
9ZQBUgTDqQZGTKHVlAhdnLS/5yjy0AIQ+d1pT/OAxqPedw7TPNbkjJbhXHweG0Eh
K/bQe+KU++N++c7oQ2Rv0DOMJ2Trd5U+TaVcrkwIy2D3Twg6kIk+Yru8P5QXfvE7
MBxY4rJOL5h51nTsYwwKRau/bwKBgQDpY8AAnOAGR1lnnhm6nSStaU9ec45vTtbg
2yXFxN5FjObk2cudEpgZOKboMrxec4VXLcgWnOw3Yk8UWJWwElIjxZwo1r45J/79
c+LP8Rh9FCE+5An1aaNrGGu4Mms1N4Wd0utJ3fkL7+mVhddlXNMYqKbamABnX9jB
q+Nx9FX/2QKBgCggUe9UPir2ppsfaxiaVA+sG1KP4ZUI4tNkl8dGZNqGhM1kNxOv
EVWQjXvWhiBPVE1RjLvJiMDF53HxQW9LqOWX0FzFRlYxGGqL2EKkLAyb9y8RXeFO
ca3m4IeNk/1ESq/AmK2fJmbvgaIt29Pj+LHkaZCIZCPKuP8Wrkd+sD1NAoGASZwa
bJcN2S0bt6CXwNHbRY5XaBTOMbEN+LFlwnCLIiiEkl1W6N16d0n06ntGCgwpXAum
detcXUN2aZZe7793hKzIyeCg8mn49HteZ/NEo/57Vdiag3qj/h0frGLKiWhPji19
5DhMWkV6yJwECYYzVi2rInqadgA23y6Vd9V2YlECgYEA3v0BT6TpfUM5BnEIX9nA
je5buofurZVpXlSb20QOhWXOD0XHhC7ZsP7xDQf5vuMTdTpFhq/hfCwUBOyI7mNR
iwRTVyGCWqSafcHSbenXEfJs4GtU5Cw9KZTWp13M5PLMwhevhwpUR+yZw7EDY7oV
PAgNmnu4pkAhHUSUxhlXcnw=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,22 @@
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
color: #333;
}
h1 {
color: #0671dc;
/* 让文字左右滚动,并且改变颜色*/
text-shadow: 0 0 5px #333;
animation: scroll-text 5s linear infinite;
animation-fill-mode: forwards;
animation-delay: 2s;
animation-direction: alternate;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
animation-play-state: running;
animation-name: scroll-text;
animation-duration: 5s;
}

View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Simple Web Server</title>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
<h1>Welcome to Simple Web Server</h1>
<p>This page is served by our custom Java web server!</p>
<p>你的请求参数是:${body}</p>
</body>
</html>