AIO 기반 초 경 량 HTTP 서버 구현
고급 대기 등급 의 웹 페이지 PPT: http://www.ipresst.com/play/52df66bb1f3a3b3448003c67
1. 많은 사람들 이 여러 해 동안 코드 를 썼 지만 Socket 을 접촉 한 적 이 없다.
2. 많은 사람들 이 여러 해 동안 웹 을 했 지만 HTTP 프로 토 콜 을 알 지 못 했다.
3. 많은 사람들 이 HTML 5 규범 을 보고 웹 소켓 을 사용 한 적 이 없다.
자바 아이 오, JDK 1.7 이후 새로 나 온 헤비급 API 는 JDK 1.4 NIO 이후 의 업그레이드 로 볼 수 있 지만 NIO 의 Reactor 모델 에 비해 AIO 의 Proactor 모델 은 운영 체제 커 널 이 읽 을 수 있 거나 쓸 수 있 는 상태 에 있 을 때 운영 체제 가 자발적으로 응용 프로그램 을 알 기 때문에 AIO 는 NIO 보다 훨씬 간단 하 다.
HTTP 프로 토 콜 을 물 어보 면 많은 사람들 이 일치 하 는 대답 은 TCP 기반 텍스트 전송 프로 토 콜 이지 만 어떻게 일 하 는 지 아 는 사람 이 적 고 가방 머리 가 무엇 인지, 가방 체 가 무엇 인지, 분할 기호 가 무엇 인지, 프로그램 이 어떻게 해 야 일사 불 란 하 게 처리 할 수 있 는 지, Keep - Alive 가 무엇 인지, 긴 폴 링 이 무엇 인지, WebSocket 의 내부 원 리 는 무엇 인지 알 수 있다.많은 사람들 이 관련 책 을 보 았 을 지 모 르 지만 책 에서 그 쳤 다. Tomcat, Netty, Jetty 의 소스 코드 는 상당히 복잡 하고 기억 하기 어렵다.TCP 프로 토 콜, HTTP 프로 토 콜 은 잠시 동안 분명하게 말 할 수 없 기 때문에 저 를 따라 한 걸음 한 걸음 가 벼 운 http - ao - server 를 실현 합 니 다. 여러분 들 이 곧 그들의 원 리 를 이해 할 수 있 을 것 이 라 고 믿 습 니 다.
서버 의 목표 1: 자주 사용 하 는 GET / POST 요청 방법 을 실현 합 니 다. GET 방법 은 URL 파 라 메 터 를 지원 합 니 다. POST 방법 은 application / x - www - form - urlencoded, multipart / form - data 와 스 트림 업 로드 를 지원 합 니 다. 그리고 모든 양 방향 웹 소켓 프로 토 콜 패키지 입 니 다.
서버 목표 2: Spring 의 개발 을 바탕 으로 Spring MVC 와 유사 한 Controller 개발 을 실현 합 니 다.
원본 코드 가 업로드 되 었 습 니 다. Maven 프로젝트 원본 코드 를 보십시오. test 디 렉 터 리 의 Bootstrap 는 직접 시작 할 수 있 습 니 다.
다음은 간소 화 된 소스 코드 입 니 다. 간단하게 훑 어보 면 AIO 처리 절 차 를 대충 알 수 있 습 니 다.
/**
* @author fangjialong
* @description , , , Socket , ,
*/
public class HttpServer {
private ExecutorService channelWorkers;
private ExecutorService processWorkers;
private AsynchronousChannelGroup workerGroup = null;
private AsynchronousServerSocketChannel serverSocket = null;
private SocketAcceptHandler socketAcceptHandler;
public synchronized void startup() throws IOException {
//
int availableProcessors = Runtime.getRuntime().availableProcessors();
channelWorkers = Executors.newFixedThreadPool(availableProcessors+1,new ProcessorThreadFactory());
workerGroup = AsynchronousChannelGroup.withCachedThreadPool(channelWorkers, 1);
serverSocket = AsynchronousServerSocketChannel.open(workerGroup);
//
serverSocket.bind(new InetSocketAddress(80), 100);
// ,
serverSocket.accept(null, socketAcceptHandler);
}
}
다음은 요청 을 받 은 후 소켓 세 션 을 만 드 는 것 입 니 다. 세 션 을 만 드 는 것 은 디 코딩 을 편리 하 게 하고 다음 HTTP 요청 을 받 아들 일 때 까지 기다 리 는 것 입 니 다.
/**
* @author cannonfang
* @name
* @date 2014-1-9
* @qq 271398203
* @todo TCP , TCP , completed
*/
@Component
public class SocketAcceptHandler implements CompletionHandler<AsynchronousSocketChannel,Object>{
private static final Logger logger = LoggerFactory.getLogger(SocketAcceptHandler.class);
private HttpServer server;
public void setServer(HttpServer server) {
this.server = server;
}
@Override
public void completed(AsynchronousSocketChannel socket,
Object obj) {
try{
SocketSession session = new SocketSession(socket,server);
// logger.debug("Socket Session({}) Create",session.hashCode());
session.read();
}catch(Throwable t){
logger.error(t.getMessage(),t);
try {
socket.close();
} catch (IOException e) {}
}finally{
server.accept();
}
}
@Override
public void failed(Throwable t, Object obj) {
server.accept();
}
}
HTTP 인 코딩 과 디 코딩 대상, HTTP 요청 의 해석 은 모두 세 부분 으로 나 뉘 어 첫 번 째 줄 을 읽 습 니 다. 이 줄 을 통 해 HTTP 요청 방법, 버 전과 요청 URI 를 판단 할 수 있 습 니 다. 줄 마다 리 턴 (ASCII: 13) 줄 바 꾸 기 (ASCII: 10) 로 나 눌 수 있 습 니 다.그리고 HTTP Header 를 읽 습 니 다. 줄 마다 (:) 와 공백 문 자 를 나 누 어 Key - Value 를 구성 합 니 다. 두 개의 리 턴 줄 을 머리 로 읽 었 습 니 다.마지막 으로 HTTP Header 에 Content - Length 헤드 가 존재 하 는 지 판단 하고, 존재 한다 면 Value 의 숫자 를 길이 로 해서 패키지 로 계속 뒤로 읽 어야 합 니 다. Content - Length: 1024 라면 1024 개의 바이트 를 패키지 로 계속 읽 어야 합 니 다.다음 코드 부분 은 Netty 소스 코드 로 갑 니 다. Netty 소스 코드 를 읽 은 학생 들 이 예전 의 그림 자 를 발견 할 것 이 라 고 믿 습 니 다.
/**
* @author cannonfang
* @name
* @date 2014-1-13
* @qq 271398203
* @todo HTTP Response Encode And Decode Class
*/
@Component
public class HttpMessageSerializer implements InitializingBean{
protected Logger logger = LoggerFactory.getLogger(HttpMessageSerializer.class);
private int maxInitialLineLength = 1024*2; //Default 2KB
private int maxHeaderSize = 1024*4; //Default 4KB
private int maxContextSize = 1024*1024*5 ;//Default 5MB
private String charset = "UTF-8";
private String dynamicSuffix;
private String defaultIndex;
private ServerConfig serverConfig;
@Autowired
public void setServerConfig(ServerConfig serverConfig) {
this.serverConfig = serverConfig;
}
@Override
public void afterPropertiesSet() throws Exception {
this.charset = serverConfig.getString("server.http.charset", charset);
logger.info("server.http.charset : {}",charset);
this.maxHeaderSize = serverConfig.getBytesLength("server.http.maxHeaderSize", this.maxHeaderSize);
logger.info("server.http.maxHeaderSize : {}",maxHeaderSize);
this.maxContextSize = serverConfig.getBytesLength("server.http.maxContextSize", this.maxContextSize);
logger.info("server.http.maxContextSize : {}",maxContextSize);
this.dynamicSuffix = serverConfig.getString("server.http.dynamic.suffix", ".do");
logger.info("server.http.dynamic.suffix : {}",dynamicSuffix);
this.defaultIndex = serverConfig.getString("server.http.index", ".html");
logger.info("server.http.dynamic.suffix : {}",this.defaultIndex);
}
public boolean decode(ByteBuffer buffer,HttpProcessor processor)throws Exception{
boolean finished = false;
DefaultHttpRequest request = null;
try{
buffer.flip();
HttpSocketStatus status = processor.getSocketStatus();
request = processor.getRequest();
switch(status){
case SKIP_CONTROL_CHARS: {
skipControlCharacters(buffer);
processor.setSocketStatus(HttpSocketStatus.READ_INITIAL);
}
case READ_INITIAL:{
String line = readLine(buffer,maxInitialLineLength);
if(line==null){
break;
}
String[] initialLine = splitInitialLine(line);
String text = initialLine[0].toUpperCase();
HttpMethod method = HttpMethod.getHttpMethod(text);
if(method==null){
throw new HttpException(HttpResponseStatus.METHOD_NOT_ALLOWED, "Unsuported HTTP Method "+text);
}
String uri = initialLine[1];
text = initialLine[2].toUpperCase();
HttpVersion version;
if (text.equals("HTTP/1.1")) {
version=HttpVersion.HTTP_1_1;
}else if (text.equals("HTTP/1.0")) {
version=HttpVersion.HTTP_1_0;
}else{
throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Unsuported HTTP Protocol "+text);
}
request = new DefaultHttpRequest(version,method,uri);
request.setCharacterEncoding(charset);
int at = uri.indexOf('?');
String queryString ;
if(at>=0){
queryString = uri.substring(0, at);
}else{
queryString = uri;
}
if(queryString.endsWith("/")){
queryString = queryString+this.defaultIndex;
request.setQueryString(queryString);
}else{
request.setQueryString(queryString);
}
if(queryString.endsWith(this.dynamicSuffix)){
request.setDynamic(true);
if(at>0){
String params = uri.substring(at);
request.decodeContentAsURL(params,charset);
}
}else{
request.setDynamic(false);
}
// logger.debug("Socket Session({}) : {}",processor.hashCode(),queryString);
processor.setRequest(request);
processor.setSocketStatus(HttpSocketStatus.READ_HEADER);
}
case READ_HEADER:{
if(!readHeaders(buffer,request)){
break;
}
long contentLength = HttpHeaders.getContentLength(request, -1);
if(request.isDynamic()){
if(contentLength>0){
if(contentLength>this.maxContextSize){
throw new HttpException(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, "Request Entity Too Large : "+contentLength);
}
try {
request.createContentBuffer((int)contentLength,request.getHeader(HttpHeaders.Names.CONTENT_TYPE));
} catch (IOException e) {
logger.info(e.getMessage(),e);
throw new HttpException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
processor.setSocketStatus(HttpSocketStatus.READ_VARIABLE_LENGTH_CONTENT);
}else{
processor.setSocketStatus(HttpSocketStatus.RUNNING);
finished=true;
break;
}
}else{
if(contentLength>0){
throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Http Static Request Do Not Suport Content Length : " + contentLength);
}else{
processor.setSocketStatus(HttpSocketStatus.RUNNING);
finished=true;
break;
}
}
}
case READ_VARIABLE_LENGTH_CONTENT:{
try {
if(request.readContentBuffer(buffer)){
processor.setSocketStatus(HttpSocketStatus.RUNNING);
finished=true;
}
} catch (IOException e) {
logger.info(e.getMessage(),e);
throw new HttpException(HttpResponseStatus.INTERNAL_SERVER_ERROR, e.getMessage());
}
break;
}
default:throw new HttpException(HttpResponseStatus.BAD_REQUEST,"Error Scoket Status : " + status);
}
}catch(Exception e){
if(request!=null){
request.destroy();
}
throw e;
}finally{
if(buffer!=null){
buffer.compact();
}
}
return finished;
}
public void encodeInitialLine(ByteBuffer buffer,HttpResponse response) throws IOException{
byte[] bytes = response.getProtocolVersion().toString().getBytes(charset);
buffer.put(bytes);
buffer.put(HttpCodecUtil.SP);
buffer.put(response.getStatus().getBytes());
buffer.put(HttpCodecUtil.CRLF);
}
public void encodeHeaders(ByteBuffer buffer,HttpResponse response,SocketSession session) throws IOException, InterruptedException, ExecutionException {
int remaining = buffer.remaining();
for(Entry<String,String> header : response.getHeaders()){
byte[] key = header.getKey().getBytes(charset);
byte[] value = header.getValue().getBytes(charset);
remaining-=key.length+value.length+3;
if(remaining<=0){
buffer.flip();
session.write(buffer).get();
remaining = buffer.remaining();
buffer.compact();
}
buffer.put(key);
buffer.put(HttpCodecUtil.COLON_SP);
buffer.put(value);
buffer.put(HttpCodecUtil.CRLF);
}
if(remaining<=0){
session.write(buffer).get();
}
buffer.put(HttpCodecUtil.CRLF);
}
public String getCharset() {
return charset;
}
private boolean readHeaders(ByteBuffer buffer,HttpRequest request) throws HttpException {
StringBuilder sb = new StringBuilder(64);
int limit = buffer.limit();
int position = buffer.position();
int lineLength = 0;
for(int index=position;index<limit;index++){
byte nextByte = buffer.get(index);
if (nextByte == HttpConstants.CR) {
nextByte = buffer.get(index+1);
if (nextByte == HttpConstants.LF) {
buffer.position(index);
if(lineLength==0){
buffer.position(index+2);
return true;
}else{
buffer.position(index);
}
readHeader(request,sb.toString());
lineLength=0;
sb.setLength(0);
index++;
}
}else if (nextByte == HttpConstants.LF) {
if(lineLength==0){
buffer.position(index+2);
return true;
}else{
buffer.position(index);
}
readHeader(request,sb.toString());
lineLength=0;
sb.setLength(0);
index++;
}else{
if (lineLength >= maxHeaderSize) {
throw new HttpException(HttpResponseStatus.BAD_REQUEST,"An HTTP header is larger than " + maxHeaderSize +" bytes.");
}
lineLength ++;
sb.append((char) nextByte);
}
}
return false;
}
private static void readHeader(HttpRequest request,String header){
String[] kv = splitHeader(header);
request.addHeader(kv[0], kv[1]);
}
private static String[] splitHeader(String sb) {
final int length = sb.length();
int nameStart;
int nameEnd;
int colonEnd;
int valueStart;
int valueEnd;
nameStart = findNonWhitespace(sb, 0);
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
char ch = sb.charAt(nameEnd);
if (ch == ':' || Character.isWhitespace(ch)) {
break;
}
}
for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
if (sb.charAt(colonEnd) == ':') {
colonEnd ++;
break;
}
}
valueStart = findNonWhitespace(sb, colonEnd);
if (valueStart == length) {
return new String[] {
sb.substring(nameStart, nameEnd),
""
};
}
valueEnd = findEndOfString(sb);
return new String[] {
sb.substring(nameStart, nameEnd),
sb.substring(valueStart, valueEnd)
};
}
private static String readLine(ByteBuffer buffer, int maxLineLength) throws HttpException {
StringBuilder sb = new StringBuilder(64);
int lineLength = 0;
int limit = buffer.limit();
int position = buffer.position();
for(int index=position;index<limit;index++){
byte nextByte = buffer.get(index);
if (nextByte == HttpConstants.CR) {
nextByte = buffer.get(index+1);
if (nextByte == HttpConstants.LF) {
buffer.position(index+2);
return sb.toString();
}
}else if (nextByte == HttpConstants.LF) {
buffer.position(index+2);
return sb.toString();
}else{
if (lineLength >= maxLineLength) {
throw new HttpException(HttpResponseStatus.REQUEST_URI_TOO_LONG,"An HTTP line is larger than " + maxLineLength +" bytes.");
}
lineLength ++;
sb.append((char) nextByte);
}
}
return null;
}
private static String[] splitInitialLine(String sb) {
int aStart;
int aEnd;
int bStart;
int bEnd;
int cStart;
int cEnd;
aStart = findNonWhitespace(sb, 0);
aEnd = findWhitespace(sb, aStart);
bStart = findNonWhitespace(sb, aEnd);
bEnd = findWhitespace(sb, bStart);
cStart = findNonWhitespace(sb, bEnd);
cEnd = findEndOfString(sb);
return new String[] {
sb.substring(aStart, aEnd),
sb.substring(bStart, bEnd),
cStart < cEnd? sb.substring(cStart, cEnd) : "" };
}
private static int findNonWhitespace(String sb, int offset) {
int result;
for (result = offset; result < sb.length(); result ++) {
if (!Character.isWhitespace(sb.charAt(result))) {
break;
}
}
return result;
}
private static int findWhitespace(String sb, int offset) {
int result;
for (result = offset; result < sb.length(); result ++) {
if (Character.isWhitespace(sb.charAt(result))) {
break;
}
}
return result;
}
private static int findEndOfString(String sb) {
int result;
for (result = sb.length(); result > 0; result --) {
if (!Character.isWhitespace(sb.charAt(result - 1))) {
break;
}
}
return result;
}
private static void skipControlCharacters(ByteBuffer buffer) {
int limit = buffer.limit();
int position = buffer.position();
for(int index=position;index<limit;index++){
char c = (char) (buffer.get(index) & 0xFF);
if (!Character.isISOControl(c) &&
!Character.isWhitespace(c)) {
buffer.position(index);
break;
}
}
}
}
다음은 Spring MVC 를 모 의 하 는 컨트롤 러 입 니 다. Spring MVC 를 직접 사용 할 수 없 는 이 유 를 묻 는 사람 이 있 을 수 있 습 니 다. Spring MVC 가 실현 하 는 기 초 는 Servlet. api 위 에 있 기 때 문 입 니 다. 즉, HttpServletRequest 인터페이스 에 반사 되 어 클래스 를 실현 할 수 있 지만 여기 서 Servlet 규범 이 완전히 실현 되 지 않 아 Spring MVC 와 어 울 리 지 않 습 니 다.그러나 그 원 리 는 매우 간단 하 다. 바로 자바 자체 의 동적 반 사 를 통 해 반사 가 성능 에 영향 을 미친다 고 말 할 수 있다. 사실 이 문 제 를 해결 하 는 것 도 어렵 지 않다. 반사 성능 손실 이 가장 큰 것 은 클래스 를 통 해 Method 대상 을 얻 는 것 이다. 서버 가 시작 할 때 모든 Controller 주 해 를 표시 한 클래스 에 대해 Method 캐 시 를 할 수 있다.요청 이 왔 을 때 Invoke 만 있 으 면 됩 니 다. 성능 손실 이 거의 없습니다.Spring MVC 의 매개 변수 반전 을 실현 하기 위해 여기 서도 모든 기본 유형, 포장 유형, 집합 유형 에 대해 판단 을 하고 분석 을 협조 하여 Spring MVC 에서 가장 자주 사용 하 는 매개 변 수 를 자동 으로 분석 하 는 역할 을 한다.
public final class MethodHandler {
private static final Logger logger = LoggerFactory.getLogger(MethodHandler.class);
private final Object object;
private final Method method;
private final boolean responseBody;
private final boolean xssFilter;
private RequestParamType[] requestParamTypes;
private int parameterLength;
private ObjectMapper objectMapper;
private boolean matcherHandler = false;
private Pattern pattern;
private String[] keys;
public MethodHandler(Object object,Method method){
this.object = object;
this.method = method;
this.responseBody=method.isAnnotationPresent(ResponseBody.class);
XssFilter filter = method.getAnnotation(XssFilter.class);
this.xssFilter = filter==null?true:filter.value();
}
public Pattern getPattern() {
return pattern;
}
public String[] getKeys() {
return keys;
}
public boolean isMatcherHandler() {
return matcherHandler;
}
void setPathPattern(Pattern pattern,String[] keys) {
this.pattern = pattern;
this.matcherHandler = true;
this.keys = keys;
}
void setObjectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
public Method getMethod() {
return method;
}
public Object getObject() {
return object;
}
void setParameterTypes(Class<?> clazz,Method method) {
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
this.parameterLength = parameterTypes.length;
this.requestParamTypes = new RequestParamType[this.parameterLength];
for(int i=0;i<this.parameterLength;i++){
Class<?> classType = parameterTypes[i];
RequestParamType paramType = new RequestParamType();
Type type;
if(classType==HttpRequest.class){
type = Type.HTTP_REQUEST;
}else if(classType==HttpResponse.class){
type = Type.HTTP_RESPONSE;
}else{
if(classType==String.class){
type = Type.STRING;
}else if(classType.isAssignableFrom(List.class)){
type = Type.LIST;
}else if(classType.isAssignableFrom(Set.class)){
type = Type.SET;
}else if(classType.isAssignableFrom(Map.class)){
type = Type.MAP;
}else if(classType.isArray()){
type = Type.ARRARY;
}else if(classType==Boolean.class||classType==boolean.class){
type = Type.BOOLEAN;
}else if(classType==Short.class||classType==short.class){
type = Type.SHORT;
}else if(classType==Integer.class||classType==int.class){
type = Type.INTEGER;
}else if(classType==Long.class||classType==long.class){
type = Type.LONG;
}else if(classType==Float.class||classType==float.class){
type = Type.FLOAT;
}else if(classType==Double.class||classType==double.class){
type = Type.DOUBLE;
}else if(classType==Character.class||classType==char.class){
type = Type.CHAR;
}else if(classType==Byte.class||classType==byte.class){
type = Type.BYTE;
}else{
throw new RuntimeException(clazz.getSimpleName()+"."+method.getName()+" param["+i+"] is not suported to request params ioc");
}
Annotation[] annotations = parameterAnnotations[i];
RequestParam requestParam = null;
PathVariable pathVariable = null;
for(Annotation annotation:annotations){
if(annotation instanceof RequestParam){
requestParam = (RequestParam)annotation;
paramType.setName(requestParam.value());
if(!requestParam.defaultValue().equals(ValueConstants.DEFAULT_NONE)){
paramType.setDefaultValue(requestParam.defaultValue());
paramType.setRequired(false);
}else{
paramType.setRequired(requestParam.required());
}
}else if(annotation instanceof PathVariable){
pathVariable = (PathVariable)annotation;
paramType.setName(pathVariable.value());
paramType.setRequired(true);
}
}
if(requestParam==null&& pathVariable == null){
throw new RuntimeException(clazz.getSimpleName()+"."+method.getName()+" param["+i+"] must be annotation present RequestParam or PathVariable");
}
}
paramType.setType(type);
this.requestParamTypes[i] = paramType;
}
}
public Object invoke(HttpRequest request,HttpResponse response) throws Throwable{
try{
if(this.parameterLength>0){
Object[] params = new Object[this.parameterLength];
for(int i=0;i<this.parameterLength;i++){
RequestParamType requestParamType = requestParamTypes[i];
Type type = requestParamType.getType();
if(type==Type.HTTP_REQUEST){
params[i]=request;
}else if(type == Type.HTTP_RESPONSE){
params[i]=response;
}else{
String name = requestParamType.getName();
String value = request.getParameter(name);
if(value==null){
value = requestParamType.getDefaultValue();
}
if(requestParamType.isRequired()&&value==null){
logger.warn("bad request http param[{}]=null",name);
throw new HttpException(HttpResponseStatus.BAD_REQUEST);
}
if(value==null){
continue;
}
switch(type){
case STRING:params[i]=value;break;
case LIST:params[i]=objectMapper.readValue(value, List.class);break;
case SET:params[i]=objectMapper.readValue(value, Set.class);break;
case MAP:params[i]=objectMapper.readValue(value, Map.class);break;
case ARRARY:params[i]=objectMapper.readValue(value, List.class).toArray();break;
case BOOLEAN:params[i]=Boolean.parseBoolean(value);break;
case SHORT:params[i]=Short.parseShort(value);break;
case INTEGER:params[i]=Integer.parseInt(value);break;
case LONG:params[i]=Long.parseLong(value);break;
case FLOAT:params[i]=Float.parseFloat(value);break;
case DOUBLE:params[i]=Double.parseDouble(value);break;
case CHAR:params[i]=value.charAt(0);break;
case BYTE:params[i]=value.getBytes()[0];break;
default:{}
}
}
}
return method.invoke(object,params);
}else{
return method.invoke(object);
}
}catch(InvocationTargetException e){
throw e.getTargetException();
}
}
public boolean isResponseBody() {
return responseBody;
}
public boolean isXssFilter(){
return xssFilter;
}
}
최종 구현 결 과 는 다음 과 같 습 니 다. Object 메시지 출력 은 기본적으로 XSS 필 터 를 사용 합 니 다.
@Controller
public class TestController {
protected Logger logger = LoggerFactory.getLogger(TestController.class);
@RequestMapping("/test1.do")
@ResponseBody
public Object test1(){
Map<String,String> result = new HashMap<String,String>();
result.put("msg", "Hello World!");
return result;
}
@RequestMapping("/test2.do")
@ResponseBody
public Object test2(
@RequestParam(required=true,value="id")String id,
@RequestParam(value="name",defaultValue="Unknown")String name){
Map<String,String> result = new HashMap<String,String>();
result.put("id", id);
result.put("name", name);
return result;
}
@RequestMapping("/multipart.do")
public String multipart(HttpRequest request) throws Exception{
FileItem file = request.getFile("file");
logger.info(file.getFieldName()+":"+file.getFieldName()+":"+file.getSize());
FileUtils.writeByteArrayToFile(new File("D://test.jpg"), file.get());
return "success";
}
@RequestMapping("/xssFilter1.do")
@ResponseBody
public Object xssFilter1(HttpRequest request,HttpResponse response){
return request.getParametersMap();
}
@RequestMapping("/xssFilter2.do")
@ResponseBody
@XssFilter(false)
public Object xssFilter2(HttpRequest request,HttpResponse response){
return request.getParametersMap();
}
@RequestMapping("/test/test/*")
@ResponseBody
public Object test10(){
Map<String,String> result = new HashMap<String,String>();
result.put("msg", "Hello World!");
return result;
}
@RequestMapping("/test/{id}/index.do")
@ResponseBody
public Object test11(@PathVariable("id")String id){
Map<String,String> result = new HashMap<String,String>();
result.put("msg", "Hello World!");
return result;
}
}
다음은 웹 소켓 프로 토 콜 패키지 입 니 다.
브 라 우 저 에서 웹 소켓 연결 을 요청 할 때 먼저 원본 HTTP 요청 을 합 니 다. 이 요청 은 보통 GET 요청 이지 만, Header (Connection) 는 Keep - Alive 가 아 닌 Upgrade 입 니 다. Header (Upgrade) 는 웹 소켓 이 며, 키 Header (Sec - webSocket - Key) 와 Header (Sec - webSocket - Version) 를 가 져 옵 니 다.최신 웹 소켓 버 전 은 13, 크롬 IE 10 Firefox 등 이 요청 한 것 은 모두 13 이다.Ajax 는 요청 Header 를 수정 할 수 있 지만 웹 소켓 규범 브 라 우 저 는 브 라 우 저 에 4 개의 Header 를 설정 할 수 없 기 때문에 서버 는 이 네 개의 머리 를 통 해 요청 의 합 법성 을 판단 하고 위 조 를 방지 해 야 합 니 다.
서버 판단 성공 후 응답 하 는 반환 코드 를 101 로 설정 합 니 다. 스 위칭 프로 토 콜, 대표 프로 토 콜 이 전환 되 고 업그레이드 와 커 넥 션 헤드 로 돌아 가 며 키 를 맞 춤 법 으로 지정 한 '258 EAFA5 - E914 - 47DA - 95CA - C5AB 0DC 85B 11' 을 통 해 SHA 1 에 서명 한 다음 Base 64 암호 화 를 통 해 되 돌려 주 고 브 라 우 저 판단 으로 예상 데 이 터 를 되 돌려 주 는 것 은 WebSocket 악수 에 성공 했다 는 뜻 이다.
아래 웹 소켓 패키지 의 프로 토 콜 규범 은 왼쪽 에서 총 32 비트 가 있 습 니 다. 1 위 는 프레임 종료 여 부 를 표시 하고 2 - 4 위 는 위 치 를 유지 합 니 다. 특정한 협상 을 하지 않 고 사용 합 니 다. 5 - 8 4 위 는 하나의 숫자 로 조작 코드 를 표시 합 니 다. 0: 계속 프레임, 1: 텍스트 프레임, 2: 2 진 프레임, 3 - 7: 미래 사용 에 사용 합 니 다. 8: 프레임 닫 기, 9: ping 프레임, A: pong 프레임, B - F: 미래 사용 에 사용 합 니 다.
9 위 는 마스크 여 부 를 표시 합 니 다. 웹 소켓 은 브 라 우 저 에서 보 내 는 메 시 지 를 마스크 해 야 합 니 다. 서버 에서 보 내 는 메 시 지 는 마스크 하지 않 아야 합 니 다.
10 - 16 비트 는 기호 가 없 는 숫자 N 의 최대 127 을 구성 하고 125 보다 작 으 면 패키지 의 길이 가 N 밖 에 없 으 며 126 또는 127 이면 확장 길 이 를 사용 하고 126 은 16 비트 확장 127 을 사용 하면 64 비트 확장 을 사용 하 며 확장 길이 중의 기호 가 없 는 숫자 는 패키지 의 길 이 를 나타 낸다.
9 위 판단 에 마스크 가 필요 하 다 면 뒤의 4 바이트 (32 비트) 를 마스크 로 하고 마스크 를 읽 은 후에 이전에 읽 은 패키지 길이 에 따라 패키지 를 읽 습 니 다. 마스크 디 코딩 규칙 은 다음 과 같 습 니 다.
for(;index<end;index++,this.payloadIndex++){
byte masked = buffer.get(index);
masked = (byte)(masked ^ (mask[(int)(this.payloadIndex%4)] & 0xFF));
buffer.put(index, masked);
}
간단 한 채 팅 방 테스트 클래스, 브 라 우 저 는 서버 가 메 시 지 를 보 내 려 고 합 니 다. 서버 가 몇 초 후에 비동기 적 으로 브 라 우 저 에 게 알 립 니 다.
public class ChartSession extends SimpleWebSocket implements WebSocketSession{
private Map<String,ChartSession> sessions;
private String name;
public ChartSession(String name ,Map<String,ChartSession> sessions) {
super(65535);
this.sessions = sessions;
this.name = name;
}
private static final Logger logger = LoggerFactory.getLogger(ChartSession.class);
private WebSocketSession session;
@Override
public void onClose() {
logger.info("{} exit",name);
sessions.remove(name);
}
@Override
public void setWebSocketSession(WebSocketSession session) {
this.session = session;
}
@Override
public void onMessage(byte[] message) {
String messageStr;
try {
messageStr = new String(message,"UTF-8");
} catch (UnsupportedEncodingException e) {
messageStr = new String(message);
}
logger.info("{} say : ",messageStr);
Iterator<ChartSession> iter = sessions.values().iterator();
byte[] m;
try {
m = (name+" say : "+messageStr).getBytes("UTF-8");
} catch (UnsupportedEncodingException e1) {
m = (name+" say : "+messageStr).getBytes();
}
while(iter.hasNext()){
ChartSession s = iter.next();
try {
s.sendText(m, 1, TimeUnit.SECONDS, null);
} catch (InterruptedException e) {}
}
}
@Override
public Future<Void> sendText(byte[] bytes, long timeout, TimeUnit unit,
WebSocketCallback callback) throws InterruptedException {
return session.sendText(bytes, timeout, unit, callback);
}
@Override
public Future<Void> sendBinary(byte[] bytes, long timeout, TimeUnit unit,
WebSocketCallback callback) throws InterruptedException {
// TODO Auto-generated method stub
return session.sendBinary(bytes, timeout, unit, callback);
}
@Override
public Future<Void> close(WebSocketCallback callback, long timeout,
TimeUnit unit) throws InterruptedException {
// TODO Auto-generated method stub
return session.close(callback, timeout, unit);
}
}
2014 - 03 - 16 - 업데이트
1. 시작 방식 을 수 정 했 고 Http 서버 는 전통 적 인 구축 을 통 해 Controller 는 Spring Context 를 사용 합 니 다.
2. Velocity Controller 모델 엔진 추가
3. Velocity 페이지 출력 deflate, gzip 출력 압축, 우선 deflate 추가
2014 - 03 - 17 - 업데이트
1. 시작 할 때 velocity 초기 화 설정 이 너무 늦 어서 spring 초기 화 클래스 에서 모드 BUG 를 찾 을 수 없습니다.
2. 핸들 러 를 찾 지 못 했 을 때 실행 할 수 있 도록 전역 차단기 의 실행 위 치 를 조정 합 니 다.
--------------2014 - 04 - 21 동아 일보
1. @ RequestParam 의 required 기본 값 을 true 로 설정
2. 지정 한 정적 디 렉 터 리 의 버그 를 해결
--------------2014 - 04 - 27 - 대한 민 국
1. 302 점프 가 Content - Length: 0 으로 돌아 가지 않 아 FireFox 가 맹청 상태 에 있다.
--------------2014 - 06 - 19 동아 일보
1. 정적 서비스 처리 클래스 를 최적화 시 켰 습 니 다. htdocs 경 로 를 처리 할 때 getAbsolutePath 에서 getCanonicalPath 로 바 꾸 었 고 디 렉 터 리 분할 기 호 를 File. separator 로 바 꾸 었 습 니 다. getAbsolutePath 는 상대 적 으로 정적 디 렉 터 리 경 로 를 지정 할 때 경로 가 혼 란 스 러 울 수 있 기 때 문 입 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.