org.java_websocket.drafts.Draft Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Java-WebSocket Show documentation
Show all versions of Java-WebSocket Show documentation
A barebones WebSocket client and server implementation written in 100% Java.
/*
* Copyright (c) 2010-2020 Nathan Rajlich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
package org.java_websocket.drafts;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import org.java_websocket.WebSocketImpl;
import org.java_websocket.enums.CloseHandshakeType;
import org.java_websocket.enums.HandshakeState;
import org.java_websocket.enums.Opcode;
import org.java_websocket.enums.Role;
import org.java_websocket.exceptions.IncompleteHandshakeException;
import org.java_websocket.exceptions.InvalidDataException;
import org.java_websocket.exceptions.InvalidHandshakeException;
import org.java_websocket.framing.BinaryFrame;
import org.java_websocket.framing.CloseFrame;
import org.java_websocket.framing.ContinuousFrame;
import org.java_websocket.framing.DataFrame;
import org.java_websocket.framing.Framedata;
import org.java_websocket.framing.TextFrame;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.handshake.ClientHandshakeBuilder;
import org.java_websocket.handshake.HandshakeBuilder;
import org.java_websocket.handshake.HandshakeImpl1Client;
import org.java_websocket.handshake.HandshakeImpl1Server;
import org.java_websocket.handshake.Handshakedata;
import org.java_websocket.handshake.ServerHandshake;
import org.java_websocket.handshake.ServerHandshakeBuilder;
import org.java_websocket.util.Charsetfunctions;
/**
* Base class for everything of a websocket specification which is not common such as the way the
* handshake is read or frames are transferred.
**/
public abstract class Draft {
/**
* In some cases the handshake will be parsed different depending on whether
*/
protected Role role = null;
protected Opcode continuousFrameType = null;
public static ByteBuffer readLine(ByteBuffer buf) {
ByteBuffer sbuf = ByteBuffer.allocate(buf.remaining());
byte prev;
byte cur = '0';
while (buf.hasRemaining()) {
prev = cur;
cur = buf.get();
sbuf.put(cur);
if (prev == (byte) '\r' && cur == (byte) '\n') {
sbuf.limit(sbuf.position() - 2);
sbuf.position(0);
return sbuf;
}
}
// ensure that there wont be any bytes skipped
buf.position(buf.position() - sbuf.position());
return null;
}
public static String readStringLine(ByteBuffer buf) {
ByteBuffer b = readLine(buf);
return b == null ? null : Charsetfunctions.stringAscii(b.array(), 0, b.limit());
}
public static HandshakeBuilder translateHandshakeHttp(ByteBuffer buf, Role role)
throws InvalidHandshakeException {
HandshakeBuilder handshake;
String line = readStringLine(buf);
if (line == null) {
throw new IncompleteHandshakeException(buf.capacity() + 128);
}
String[] firstLineTokens = line.split(" ", 3);// eg. HTTP/1.1 101 Switching the Protocols
if (firstLineTokens.length != 3) {
throw new InvalidHandshakeException();
}
if (role == Role.CLIENT) {
handshake = translateHandshakeHttpClient(firstLineTokens, line);
} else {
handshake = translateHandshakeHttpServer(firstLineTokens, line);
}
line = readStringLine(buf);
while (line != null && line.length() > 0) {
String[] pair = line.split(":", 2);
if (pair.length != 2) {
throw new InvalidHandshakeException("not an http header");
}
// If the handshake contains already a specific key, append the new value
if (handshake.hasFieldValue(pair[0])) {
handshake.put(pair[0],
handshake.getFieldValue(pair[0]) + "; " + pair[1].replaceFirst("^ +", ""));
} else {
handshake.put(pair[0], pair[1].replaceFirst("^ +", ""));
}
line = readStringLine(buf);
}
if (line == null) {
throw new IncompleteHandshakeException();
}
return handshake;
}
/**
* Checking the handshake for the role as server
*
* @param firstLineTokens the token of the first line split as as an string array
* @param line the whole line
* @return a handshake
*/
private static HandshakeBuilder translateHandshakeHttpServer(String[] firstLineTokens,
String line) throws InvalidHandshakeException {
// translating/parsing the request from the CLIENT
if (!"GET".equalsIgnoreCase(firstLineTokens[0])) {
throw new InvalidHandshakeException(String
.format("Invalid request method received: %s Status line: %s", firstLineTokens[0], line));
}
if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[2])) {
throw new InvalidHandshakeException(String
.format("Invalid status line received: %s Status line: %s", firstLineTokens[2], line));
}
ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client();
clienthandshake.setResourceDescriptor(firstLineTokens[1]);
return clienthandshake;
}
/**
* Checking the handshake for the role as client
*
* @param firstLineTokens the token of the first line split as as an string array
* @param line the whole line
* @return a handshake
*/
private static HandshakeBuilder translateHandshakeHttpClient(String[] firstLineTokens,
String line) throws InvalidHandshakeException {
// translating/parsing the response from the SERVER
if (!"101".equals(firstLineTokens[1])) {
throw new InvalidHandshakeException(String
.format("Invalid status code received: %s Status line: %s", firstLineTokens[1], line));
}
if (!"HTTP/1.1".equalsIgnoreCase(firstLineTokens[0])) {
throw new InvalidHandshakeException(String
.format("Invalid status line received: %s Status line: %s", firstLineTokens[0], line));
}
HandshakeBuilder handshake = new HandshakeImpl1Server();
ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake;
serverhandshake.setHttpStatus(Short.parseShort(firstLineTokens[1]));
serverhandshake.setHttpStatusMessage(firstLineTokens[2]);
return handshake;
}
public abstract HandshakeState acceptHandshakeAsClient(ClientHandshake request,
ServerHandshake response) throws InvalidHandshakeException;
public abstract HandshakeState acceptHandshakeAsServer(ClientHandshake handshakedata)
throws InvalidHandshakeException;
protected boolean basicAccept(Handshakedata handshakedata) {
return handshakedata.getFieldValue("Upgrade").equalsIgnoreCase("websocket") && handshakedata
.getFieldValue("Connection").toLowerCase(Locale.ENGLISH).contains("upgrade");
}
public abstract ByteBuffer createBinaryFrame(Framedata framedata);
public abstract List createFrames(ByteBuffer binary, boolean mask);
public abstract List createFrames(String text, boolean mask);
/**
* Handle the frame specific to the draft
*
* @param webSocketImpl the websocketimpl used for this draft
* @param frame the frame which is supposed to be handled
* @throws InvalidDataException will be thrown on invalid data
*/
public abstract void processFrame(WebSocketImpl webSocketImpl, Framedata frame)
throws InvalidDataException;
public List continuousFrame(Opcode op, ByteBuffer buffer, boolean fin) {
if (op != Opcode.BINARY && op != Opcode.TEXT) {
throw new IllegalArgumentException("Only Opcode.BINARY or Opcode.TEXT are allowed");
}
DataFrame bui = null;
if (continuousFrameType != null) {
bui = new ContinuousFrame();
} else {
continuousFrameType = op;
if (op == Opcode.BINARY) {
bui = new BinaryFrame();
} else if (op == Opcode.TEXT) {
bui = new TextFrame();
}
}
bui.setPayload(buffer);
bui.setFin(fin);
try {
bui.isValid();
} catch (InvalidDataException e) {
throw new IllegalArgumentException(
e); // can only happen when one builds close frames(Opcode.Close)
}
if (fin) {
continuousFrameType = null;
} else {
continuousFrameType = op;
}
return Collections.singletonList((Framedata) bui);
}
public abstract void reset();
/**
* @deprecated use createHandshake without the role
*/
@Deprecated
public List createHandshake(Handshakedata handshakedata, Role ownrole) {
return createHandshake(handshakedata);
}
public List createHandshake(Handshakedata handshakedata) {
return createHandshake(handshakedata, true);
}
/**
* @deprecated use createHandshake without the role since it does not have any effect
*/
@Deprecated
public List createHandshake(Handshakedata handshakedata, Role ownrole,
boolean withcontent) {
return createHandshake(handshakedata, withcontent);
}
public List createHandshake(Handshakedata handshakedata, boolean withcontent) {
StringBuilder bui = new StringBuilder(100);
if (handshakedata instanceof ClientHandshake) {
bui.append("GET ").append(((ClientHandshake) handshakedata).getResourceDescriptor())
.append(" HTTP/1.1");
} else if (handshakedata instanceof ServerHandshake) {
bui.append("HTTP/1.1 101 ").append(((ServerHandshake) handshakedata).getHttpStatusMessage());
} else {
throw new IllegalArgumentException("unknown role");
}
bui.append("\r\n");
Iterator it = handshakedata.iterateHttpFields();
while (it.hasNext()) {
String fieldname = it.next();
String fieldvalue = handshakedata.getFieldValue(fieldname);
bui.append(fieldname);
bui.append(": ");
bui.append(fieldvalue);
bui.append("\r\n");
}
bui.append("\r\n");
byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString());
byte[] content = withcontent ? handshakedata.getContent() : null;
ByteBuffer bytebuffer = ByteBuffer
.allocate((content == null ? 0 : content.length) + httpheader.length);
bytebuffer.put(httpheader);
if (content != null) {
bytebuffer.put(content);
}
bytebuffer.flip();
return Collections.singletonList(bytebuffer);
}
public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient(
ClientHandshakeBuilder request) throws InvalidHandshakeException;
public abstract HandshakeBuilder postProcessHandshakeResponseAsServer(ClientHandshake request,
ServerHandshakeBuilder response) throws InvalidHandshakeException;
public abstract List translateFrame(ByteBuffer buffer) throws InvalidDataException;
public abstract CloseHandshakeType getCloseHandshakeType();
/**
* Drafts must only be by one websocket at all. To prevent drafts to be used more than once the
* Websocket implementation should call this method in order to create a new usable version of a
* given draft instance.
The copy can be safely used in conjunction with a new websocket
* connection.
*
* @return a copy of the draft
*/
public abstract Draft copyInstance();
public Handshakedata translateHandshake(ByteBuffer buf) throws InvalidHandshakeException {
return translateHandshakeHttp(buf, role);
}
public int checkAlloc(int bytecount) throws InvalidDataException {
if (bytecount < 0) {
throw new InvalidDataException(CloseFrame.PROTOCOL_ERROR, "Negative count");
}
return bytecount;
}
int readVersion(Handshakedata handshakedata) {
String vers = handshakedata.getFieldValue("Sec-WebSocket-Version");
if (vers.length() > 0) {
int v;
try {
v = Integer.parseInt(vers.trim());
return v;
} catch (NumberFormatException e) {
return -1;
}
}
return -1;
}
public void setParseMode(Role role) {
this.role = role;
}
public Role getRole() {
return role;
}
public String toString() {
return getClass().getSimpleName();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy