001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.socket.sockjs.transport.handler; 018 019import java.io.IOException; 020import java.nio.charset.StandardCharsets; 021import java.util.regex.Pattern; 022 023import org.springframework.http.MediaType; 024import org.springframework.http.server.ServerHttpRequest; 025import org.springframework.http.server.ServerHttpResponse; 026import org.springframework.lang.Nullable; 027import org.springframework.util.MultiValueMap; 028import org.springframework.util.StringUtils; 029import org.springframework.web.socket.WebSocketHandler; 030import org.springframework.web.socket.sockjs.SockJsException; 031import org.springframework.web.socket.sockjs.frame.SockJsFrame; 032import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; 033import org.springframework.web.socket.sockjs.transport.SockJsSession; 034import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory; 035import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; 036import org.springframework.web.util.UriComponentsBuilder; 037import org.springframework.web.util.UriUtils; 038 039/** 040 * Base class for HTTP transport handlers that push messages to connected clients. 041 * 042 * @author Rossen Stoyanchev 043 * @since 4.0 044 */ 045public abstract class AbstractHttpSendingTransportHandler extends AbstractTransportHandler 046 implements SockJsSessionFactory { 047 048 /** 049 * Pattern for validating callback parameter values. 050 */ 051 private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_.]*"); 052 053 054 @Override 055 public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, 056 WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException { 057 058 AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; 059 060 // https://github.com/sockjs/sockjs-client/issues/130 061 // sockJsSession.setAcceptedProtocol(protocol); 062 063 // Set content type before writing 064 response.getHeaders().setContentType(getContentType()); 065 066 handleRequestInternal(request, response, sockJsSession); 067 } 068 069 protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, 070 AbstractHttpSockJsSession sockJsSession) throws SockJsException { 071 072 if (sockJsSession.isNew()) { 073 if (logger.isDebugEnabled()) { 074 logger.debug(request.getMethod() + " " + request.getURI()); 075 } 076 sockJsSession.handleInitialRequest(request, response, getFrameFormat(request)); 077 } 078 else if (sockJsSession.isClosed()) { 079 if (logger.isDebugEnabled()) { 080 logger.debug("Connection already closed (but not removed yet) for " + sockJsSession); 081 } 082 SockJsFrame frame = SockJsFrame.closeFrameGoAway(); 083 try { 084 response.getBody().write(frame.getContentBytes()); 085 } 086 catch (IOException ex) { 087 throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex); 088 } 089 } 090 else if (!sockJsSession.isActive()) { 091 if (logger.isTraceEnabled()) { 092 logger.trace("Starting " + getTransportType() + " async request."); 093 } 094 sockJsSession.handleSuccessiveRequest(request, response, getFrameFormat(request)); 095 } 096 else { 097 if (logger.isDebugEnabled()) { 098 logger.debug("Another " + getTransportType() + " connection still open for " + sockJsSession); 099 } 100 String formattedFrame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen()); 101 try { 102 response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); 103 } 104 catch (IOException ex) { 105 throw new SockJsException("Failed to send " + formattedFrame, sockJsSession.getId(), ex); 106 } 107 } 108 } 109 110 111 protected abstract MediaType getContentType(); 112 113 protected abstract SockJsFrameFormat getFrameFormat(ServerHttpRequest request); 114 115 116 @Nullable 117 protected final String getCallbackParam(ServerHttpRequest request) { 118 String query = request.getURI().getQuery(); 119 MultiValueMap<String, String> params = UriComponentsBuilder.newInstance().query(query).build().getQueryParams(); 120 String value = params.getFirst("c"); 121 if (!StringUtils.hasLength(value)) { 122 return null; 123 } 124 String result = UriUtils.decode(value, StandardCharsets.UTF_8); 125 return (CALLBACK_PARAM_PATTERN.matcher(result).matches() ? result : null); 126 } 127 128}