/* * ResponseBuilder.java February 2014 * * Copyright (C) 2014, Niall Gallagher <niallg@users.sf.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.http.socket.service; import static org.simpleframework.http.Protocol.CLOSE; import static org.simpleframework.http.Protocol.CONNECTION; import static org.simpleframework.http.Protocol.DATE; import static org.simpleframework.http.Protocol.SEC_WEBSOCKET_ACCEPT; import static org.simpleframework.http.Protocol.UPGRADE; import static org.simpleframework.http.Protocol.WEBSOCKET; import static org.simpleframework.http.socket.service.ServiceEvent.WRITE_HEADER; import java.io.IOException; import org.simpleframework.http.Request; import org.simpleframework.http.Response; import org.simpleframework.http.Status; import org.simpleframework.transport.Channel; import org.simpleframework.transport.ByteWriter; import org.simpleframework.transport.trace.Trace; /** * The <code>ResponseBuilder</code> object is used to build a response * to a WebSocket handshake. In order for a successful handshake to * complete a HTTP request must have a version of 13 referring * to RFC 6455, a WebSocket key, and the required HTTP connection * details. If any of these are missing the server is obliged to * respond with a HTTP 400 response indicating a bad request. * * @author Niall Gallagher */ class ResponseBuilder { /** * This is used to validate the initiating WebSocket request. */ private final RequestValidator validator; /** * This is the accept token generated for the request. */ private final AcceptToken token; /** * This is the sender used to send the WebSocket response. */ private final ByteWriter writer; /** * This is the response to the WebSocket handshake. */ private final Response response; /** * This is the underlying TCP channel for the request. */ private final Channel channel; /** * This is used to trace the activity for the handshake. */ private final Trace trace; /** * Constructor for the <code>ResponseBuilder</code> object. In order * to process the WebSocket handshake this requires the original * request and the response as well as the underlying TCP channel * which forms the basis of the WebSocket connection. * * @param request this is the request that initiated the handshake * @param response this is the response for the handshake */ public ResponseBuilder(Request request, Response response) throws Exception { this.validator = new RequestValidator(request); this.token = new AcceptToken(request); this.channel = request.getChannel(); this.writer = channel.getWriter(); this.trace = channel.getTrace(); this.response = response; } /** * This is used to determine if the client handshake request had * all the required headers as dictated by RFC 6455 section 4.2.1. * If the request does not contain any of these parts then this * will return false, indicating a HTTP 400 response is sent to * the client, otherwise a HTTP 101 response is sent. */ public void commit() throws IOException { if(validator.isValid()) { accept(); } else { reject(); } } /** * This is used to respond to the client with a HTTP 400 response * indicating the WebSocket handshake failed. No response body is * sent with the rejection message and the underlying TCP channel * is closed to prevent further use of the connection. */ private void reject() throws IOException { long time = System.currentTimeMillis(); response.setStatus(Status.BAD_REQUEST); response.setValue(CONNECTION, CLOSE); response.setDate(DATE, time); String header = response.toString(); byte[] message = header.getBytes("UTF-8"); trace.trace(WRITE_HEADER, header); writer.write(message); writer.flush(); writer.close(); } /** * This is used to respond to the client with a HTTP 101 response * to indicate that the WebSocket handshake succeeeded. Once this * response has been sent all traffic between the client and * server will be with WebSocket frames as defined by RFC 6455. */ private void accept() throws IOException { long time = System.currentTimeMillis(); String accept = token.create(); response.setStatus(Status.SWITCHING_PROTOCOLS); response.setDescription(UPGRADE); response.setValue(CONNECTION, UPGRADE); response.setDate(DATE, time); response.setValue(SEC_WEBSOCKET_ACCEPT, accept); response.setValue(UPGRADE, WEBSOCKET); String header = response.toString(); byte[] message = header.getBytes("UTF-8"); trace.trace(WRITE_HEADER, header); writer.write(message); writer.flush(); } }