com.hibegin.http.server.impl.SimpleHttpResponse Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of simplewebserver Show documentation
Show all versions of simplewebserver Show documentation
Simple, flexible, less dependent, more extended. Less memory footprint, can quickly build Web project.
Can quickly run embedded, Android devices
package com.hibegin.http.server.impl;
import com.hibegin.common.util.BytesUtil;
import com.hibegin.common.util.LoggerUtil;
import com.hibegin.common.util.ObjectUtil;
import com.hibegin.http.io.ChunkedOutputStream;
import com.hibegin.http.io.GzipCompressingInputStream;
import com.hibegin.http.io.LengthByteArrayInputStream;
import com.hibegin.http.server.api.HttpRequest;
import com.hibegin.http.server.api.HttpResponse;
import com.hibegin.http.server.config.ResponseConfig;
import com.hibegin.http.server.execption.InternalException;
import com.hibegin.http.server.util.MimeTypeUtil;
import com.hibegin.http.server.util.PathUtil;
import com.hibegin.http.server.util.StatusCodeUtil;
import com.hibegin.http.server.web.cookie.Cookie;
import com.hibegin.template.BasicTemplateRender;
import com.hibegin.template.FreemarkerTemplateRender;
import java.io.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SimpleHttpResponse implements HttpResponse {
private static final String CRLF = "\r\n";
private static final int RESPONSE_BYTES_BLANK_SIZE = 4096;
private static final Logger LOGGER = LoggerUtil.getLogger(SimpleHttpResponse.class);
private final Map header = new TreeMap<>();
private final HttpRequest request;
private final List cookieList = new ArrayList<>();
private final ResponseConfig responseConfig;
public SimpleHttpResponse(HttpRequest request, ResponseConfig responseConfig) {
this.request = request;
this.responseConfig = responseConfig;
}
private static final List textContentTypes = Arrays.asList(
"application/json",
"application/xml",
"application/javascript",
"application/x-www-form-urlencoded",
"application/vnd.api+json",
"application/x-yaml"
);
private boolean isTextContent(String contentType) {
return contentType.startsWith("text/") || textContentTypes.contains(contentType);
}
@Override
public void writeFile(File file) {
if (!file.exists()) {
renderByStatusCode(404);
return;
}
if (file.isDirectory()) {
renderByStatusCode(302);
return;
}
try (FileInputStream fileInputStream = new FileInputStream(file)) {
String ext = file.getName().substring(file.getName().lastIndexOf(".") + 1);
// getMimeType
trySetResponseContentType(MimeTypeUtil.getMimeStrByExt(ext));
write(fileInputStream, 200, file.length());
} catch (FileNotFoundException e) {
renderByStatusCode(404);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "", e);
}
}
private void send(byte[] bytes, boolean close) {
if (Objects.isNull(request.getHandler())) {
if (!request.getServerConfig().isNativeImageAgent()) {
LOGGER.warning("Request missing channel handler");
}
return;
}
try {
if (bytes.length > 0) {
request.getHandler().handleWrite(ByteBuffer.wrap(bytes));
}
if (close) {
request.getHandler().close();
}
} catch (IOException e) {
request.getHandler().close();
//LOGGER.log(Level.WARNING, "send error " + e.getMessage());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "send error", e);
throw new InternalException("send error", e);
}
}
@Override
public void send(ByteArrayOutputStream outputStream, boolean close) {
send(outputStream.toByteArray(), close);
}
private void send(byte[] bytes) {
send(bytes, "close".equalsIgnoreCase(getHeader().get("Connection")));
}
@Override
public void renderJson(Object obj) {
try {
renderByMimeType("json", request.getServerConfig().getHttpJsonMessageConverter().toJson(obj).getBytes());
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "", e);
throw new InternalException(e);
}
}
private byte[] wrapperBaseResponseHeader(int statusCode) {
header.put("Server", request.getServerConfig().getServerInfo());
if (!getHeader().containsKey("Connection")) {
boolean keepAlive = request.getHeader("Connection") == null;
if (keepAlive) {
String httpVersion = request.getHttpVersion();
if ("".equals(httpVersion.trim()) || "HTTP/1.0".equals(httpVersion)) {
getHeader().put("Connection", "close");
} else {
getHeader().put("Connection", "keep-alive");
}
} else if (!"close".equals(request.getHeader("Connection"))) {
getHeader().put("Connection", "keep-alive");
} else {
getHeader().put("Connection", "close");
}
}
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 ").append(statusCode).append(" ").append(StatusCodeUtil.getStatusCodeDesc(statusCode)).append(CRLF);
for (Entry he : header.entrySet()) {
sb.append(he.getKey()).append(": ").append(he.getValue()).append(CRLF);
}
//deal cookie
if (!responseConfig.isDisableCookie()) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie != null && cookie.isCreate()) {
cookieList.add(cookie);
}
}
}
for (Cookie cookie : cookieList) {
sb.append("Set-Cookie: ").append(cookie).append(CRLF);
}
}
sb.append(CRLF);
return sb.toString().getBytes();
}
private String getHtmlStrByStatusCode(int statusCode) {
return "" + statusCode + " " + StatusCodeUtil.getStatusCodeDesc(statusCode) + " " + statusCode + " " + StatusCodeUtil.getStatusCodeDesc(statusCode) + "
" + request.getServerConfig().getServerInfo() + " ";
}
private void renderByStatusCode(int errorCode) {
if (errorCode > 399) {
renderByMimeType("html", getHtmlStrByStatusCode(errorCode).getBytes(), errorCode);
} else if (errorCode > 299) {
if (!header.containsKey("Location")) {
String welcomeFile = request.getServerConfig().getWelcomeFile();
if (welcomeFile == null || "".equals(welcomeFile.trim())) {
header.put("Location", request.getScheme() + "://" + request.getHeader("Host") + "/" + request.getUri() + welcomeFile);
}
}
renderByMimeType("", null, errorCode);
}
}
@Override
public void renderCode(int code) {
renderByStatusCode(code);
}
@Override
public void renderHtml(String htmlPath) {
writeFile(PathUtil.getStaticFile(htmlPath));
}
@Override
public void addCookie(Cookie cookie) {
cookieList.add(cookie);
}
@Override
public void renderHtmlStr(String htmlContent) {
try {
renderByMimeType("html", htmlContent.getBytes(responseConfig.getCharSet()));
} catch (UnsupportedEncodingException e) {
LOGGER.log(Level.SEVERE, "", e);
}
}
private void renderByMimeType(String ext, byte[] body) {
renderByMimeType(ext, body, 200);
}
private void renderByMimeType(String ext, byte[] body, int code) {
if (ext != null && !ext.isEmpty()) {
trySetResponseContentType(MimeTypeUtil.getMimeStrByExt(ext));
}
if (body != null && body.length > 0) {
write(new ByteArrayInputStream(body), code, body.length);
} else {
write(null, code, -1);
}
}
private void trySetResponseContentType(String contentType) {
if (getHeader("Content-Type") == null) {
String cType = contentType;
if (!cType.contains(";") && isTextContent(cType)) {
cType = contentType + ";charset=" + responseConfig.getCharSet();
}
header.put("Content-Type", cType);
}
}
private String getHeader(String key) {
String headerValue = header.get(key);
if (headerValue != null) {
return headerValue;
}
for (Map.Entry entry : header.entrySet()) {
if (entry.getKey().equalsIgnoreCase(key)) {
return entry.getValue();
}
}
return null;
}
@Override
public void addHeader(String name, String value) {
if (name.equalsIgnoreCase("Content-type")) {
trySetResponseContentType(value);
return;
}
header.put(name, value);
}
@Override
public void redirect(String url) {
header.put("Location", url);
renderByMimeType("", null, 302);
}
@Override
public void forward(String uri) {
redirect(request.getScheme() + "://" + request.getHeader("Host") + "/" + uri);
}
@Override
public void renderFile(File file) {
if (file.exists()) {
header.put("Content-Disposition", "attachment;filename=\"" + file.getName() + "\"");
writeFile(file);
} else {
renderCode(404);
}
}
@Override
public void renderFreeMarker(String name) {
try {
renderHtmlStr(new FreemarkerTemplateRender(request).renderByTemplateName(name));
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "", e);
throw new InternalException(e);
}
}
@Override
public void renderBasicTemplate(String name) {
try {
renderHtmlStr(new BasicTemplateRender(request.getAttr(), ObjectUtil.requireNonNullElse(request.getServerConfig().getBasicTemplateClass(), SimpleHttpResponse.class)).renderByTemplateName(name));
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "", e);
throw new InternalException(e);
}
}
@Override
public void write(InputStream inputStream) {
write(inputStream, 200);
}
@Override
public void write(InputStream inputStream, int code) {
write(inputStream, code, -1);
}
private boolean needChunked(InputStream inputStream, long bodyLength) {
if (inputStream == null) {
return false;
}
if (bodyLength < 0) {
return true;
}
if (bodyLength == 0) {
return false;
}
return isGzip();
}
private boolean isGzip() {
if (!responseConfig.isEnableGzip()) {
return false;
}
String requestHeader = request.getHeader("Accept-Encoding");
if (Objects.isNull(requestHeader) || requestHeader.trim().isEmpty()) {
return false;
}
if (!requestHeader.contains("gzip")) {
return false;
}
String contentType = getHeader("Content-Type");
if (Objects.isNull(contentType) || contentType.trim().isEmpty()) {
return false;
}
return responseConfig.getGzipMimeTypes().stream().anyMatch(contentType::contains);
}
private byte[] toChunked(byte[] inputBytes) throws IOException {
ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(tmpOut);
chunkedOutputStream.write(inputBytes);
return tmpOut.toByteArray();
}
private void write(InputStream inputStream, int code, long bodyLength) {
try {
//处理流,避免不传输实际的文件大小
if (Objects.nonNull(inputStream) && inputStream instanceof FileInputStream) {
FileInputStream fin = (FileInputStream) inputStream;
bodyLength = fin.getChannel().size();
} else if (Objects.nonNull(inputStream) && inputStream instanceof LengthByteArrayInputStream) {
LengthByteArrayInputStream lengthByteArrayInputStream = (LengthByteArrayInputStream) inputStream;
bodyLength = lengthByteArrayInputStream.getLength();
}
boolean chunked = needChunked(inputStream, bodyLength);
if (chunked) {
header.put("Transfer-Encoding", "chunked");
header.remove("Content-Length");
//启动gzip
if (isGzip()) {
header.put("Content-Encoding", "gzip");
inputStream = new GzipCompressingInputStream(inputStream);
}
} else {
header.put("Content-Length", Math.max(bodyLength, 0) + "");
}
send(wrapperBaseResponseHeader(code), false);
if (inputStream == null) {
send(new byte[0]);
return;
}
byte[] bytes = new byte[RESPONSE_BYTES_BLANK_SIZE];
int length;
while ((length = inputStream.read(bytes)) != -1) {
if (chunked) {
send(toChunked(BytesUtil.subBytes(bytes, 0, length)), false);
} else {
send(BytesUtil.subBytes(bytes, 0, length), false);
}
}
if (chunked) {
ByteArrayOutputStream tmpOut = new ByteArrayOutputStream();
ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(tmpOut);
chunkedOutputStream.close();
send(tmpOut.toByteArray());
} else {
send(new byte[0]);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "", e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "", e);
}
}
}
}
@Override
public void write(ByteArrayOutputStream outputStream, int code) {
write(new ByteArrayInputStream(outputStream.toByteArray()), code);
}
@Override
public Map getHeader() {
return header;
}
@Override
public void renderText(String text) {
try {
renderByMimeType("text", text.getBytes(responseConfig.getCharSet()));
} catch (UnsupportedEncodingException e) {
LOGGER.log(Level.SEVERE, "", e);
throw new InternalException(e);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy