001/* 002 * Copyright 2002-2015 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.io.UnsupportedEncodingException; 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.util.MultiValueMap; 027import org.springframework.util.StringUtils; 028import org.springframework.web.socket.WebSocketHandler; 029import org.springframework.web.socket.sockjs.SockJsException; 030import org.springframework.web.socket.sockjs.frame.SockJsFrame; 031import org.springframework.web.socket.sockjs.frame.SockJsFrameFormat; 032import org.springframework.web.socket.sockjs.transport.SockJsSession; 033import org.springframework.web.socket.sockjs.transport.SockJsSessionFactory; 034import org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession; 035import org.springframework.web.util.UriComponentsBuilder; 036import org.springframework.web.util.UriUtils; 037 038/** 039 * Base class for HTTP transport handlers that push messages to connected clients. 040 * 041 * @author Rossen Stoyanchev 042 * @since 4.0 043 */ 044public abstract class AbstractHttpSendingTransportHandler extends AbstractTransportHandler 045 implements SockJsSessionFactory { 046 047 /** 048 * Pattern for validating jsonp callback parameter values. 049 */ 050 private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*"); 051 052 053 @Override 054 public final void handleRequest(ServerHttpRequest request, ServerHttpResponse response, 055 WebSocketHandler wsHandler, SockJsSession wsSession) throws SockJsException { 056 057 AbstractHttpSockJsSession sockJsSession = (AbstractHttpSockJsSession) wsSession; 058 059 String protocol = null; // https://github.com/sockjs/sockjs-client/issues/130 060 sockJsSession.setAcceptedProtocol(protocol); 061 062 // Set content type before writing 063 response.getHeaders().setContentType(getContentType()); 064 065 handleRequestInternal(request, response, sockJsSession); 066 } 067 068 protected void handleRequestInternal(ServerHttpRequest request, ServerHttpResponse response, 069 AbstractHttpSockJsSession sockJsSession) throws SockJsException { 070 071 if (sockJsSession.isNew()) { 072 if (logger.isDebugEnabled()) { 073 logger.debug(request.getMethod() + " " + request.getURI()); 074 } 075 sockJsSession.handleInitialRequest(request, response, getFrameFormat(request)); 076 } 077 else if (sockJsSession.isClosed()) { 078 if (logger.isDebugEnabled()) { 079 logger.debug("Connection already closed (but not removed yet) for " + sockJsSession); 080 } 081 SockJsFrame frame = SockJsFrame.closeFrameGoAway(); 082 try { 083 response.getBody().write(frame.getContentBytes()); 084 } 085 catch (IOException ex) { 086 throw new SockJsException("Failed to send " + frame, sockJsSession.getId(), ex); 087 } 088 } 089 else if (!sockJsSession.isActive()) { 090 if (logger.isTraceEnabled()) { 091 logger.trace("Starting " + getTransportType() + " async request."); 092 } 093 sockJsSession.handleSuccessiveRequest(request, response, getFrameFormat(request)); 094 } 095 else { 096 if (logger.isDebugEnabled()) { 097 logger.debug("Another " + getTransportType() + " connection still open for " + sockJsSession); 098 } 099 String formattedFrame = getFrameFormat(request).format(SockJsFrame.closeFrameAnotherConnectionOpen()); 100 try { 101 response.getBody().write(formattedFrame.getBytes(SockJsFrame.CHARSET)); 102 } 103 catch (IOException ex) { 104 throw new SockJsException("Failed to send " + formattedFrame, sockJsSession.getId(), ex); 105 } 106 } 107 } 108 109 110 protected abstract MediaType getContentType(); 111 112 protected abstract SockJsFrameFormat getFrameFormat(ServerHttpRequest request); 113 114 115 protected final String getCallbackParam(ServerHttpRequest request) { 116 String query = request.getURI().getQuery(); 117 MultiValueMap<String, String> params = UriComponentsBuilder.newInstance().query(query).build().getQueryParams(); 118 String value = params.getFirst("c"); 119 if (StringUtils.isEmpty(value)) { 120 return null; 121 } 122 try { 123 String result = UriUtils.decode(value, "UTF-8"); 124 return (CALLBACK_PARAM_PATTERN.matcher(result).matches() ? result : null); 125 } 126 catch (UnsupportedEncodingException ex) { 127 // should never happen 128 throw new SockJsException("Unable to decode callback query parameter", null, ex); 129 } 130 } 131 132}