001/* 002 * Copyright 2002-2017 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.frame; 018 019import java.nio.charset.Charset; 020 021import org.springframework.util.Assert; 022 023/** 024 * Represents a SockJS frame. Provides factory methods to create SockJS frames. 025 * 026 * @author Rossen Stoyanchev 027 * @since 4.0 028 */ 029public class SockJsFrame { 030 031 public static final Charset CHARSET = Charset.forName("UTF-8"); 032 033 private static final SockJsFrame OPEN_FRAME = new SockJsFrame("o"); 034 035 private static final SockJsFrame HEARTBEAT_FRAME = new SockJsFrame("h"); 036 037 private static final SockJsFrame CLOSE_GO_AWAY_FRAME = closeFrame(3000, "Go away!"); 038 039 private static final SockJsFrame CLOSE_ANOTHER_CONNECTION_OPEN_FRAME = 040 closeFrame(2010, "Another connection still open"); 041 042 043 private final SockJsFrameType type; 044 045 private final String content; 046 047 048 /** 049 * Create a new instance frame with the given frame content. 050 * @param content the content (must be a non-empty and represent a valid SockJS frame) 051 */ 052 public SockJsFrame(String content) { 053 Assert.hasText(content, "Content must not be empty"); 054 if ("o".equals(content)) { 055 this.type = SockJsFrameType.OPEN; 056 this.content = content; 057 } 058 else if ("h".equals(content)) { 059 this.type = SockJsFrameType.HEARTBEAT; 060 this.content = content; 061 } 062 else if (content.charAt(0) == 'a') { 063 this.type = SockJsFrameType.MESSAGE; 064 this.content = (content.length() > 1 ? content : "a[]"); 065 } 066 else if (content.charAt(0) == 'm') { 067 this.type = SockJsFrameType.MESSAGE; 068 this.content = (content.length() > 1 ? content : "null"); 069 } 070 else if (content.charAt(0) == 'c') { 071 this.type = SockJsFrameType.CLOSE; 072 this.content = (content.length() > 1 ? content : "c[]"); 073 } 074 else { 075 throw new IllegalArgumentException("Unexpected SockJS frame type in content \"" + content + "\""); 076 } 077 } 078 079 080 /** 081 * Return the SockJS frame type. 082 */ 083 public SockJsFrameType getType() { 084 return this.type; 085 } 086 087 /** 088 * Return the SockJS frame content (never {@code null}). 089 */ 090 public String getContent() { 091 return this.content; 092 } 093 094 /** 095 * Return the SockJS frame content as a byte array. 096 */ 097 public byte[] getContentBytes() { 098 return this.content.getBytes(CHARSET); 099 } 100 101 /** 102 * Return data contained in a SockJS "message" and "close" frames. Otherwise 103 * for SockJS "open" and "close" frames, which do not contain data, return 104 * {@code null}. 105 */ 106 public String getFrameData() { 107 if (getType() == SockJsFrameType.OPEN || getType() == SockJsFrameType.HEARTBEAT) { 108 return null; 109 } 110 else { 111 return getContent().substring(1); 112 } 113 } 114 115 116 @Override 117 public boolean equals(Object other) { 118 if (this == other) { 119 return true; 120 } 121 if (!(other instanceof SockJsFrame)) { 122 return false; 123 } 124 SockJsFrame otherFrame = (SockJsFrame) other; 125 return (this.type.equals(otherFrame.type) && this.content.equals(otherFrame.content)); 126 } 127 128 @Override 129 public int hashCode() { 130 return this.content.hashCode(); 131 } 132 133 @Override 134 public String toString() { 135 String result = this.content; 136 if (result.length() > 80) { 137 result = result.substring(0, 80) + "...(truncated)"; 138 } 139 return "SockJsFrame content='" + result.replace("\n", "\\n").replace("\r", "\\r") + "'"; 140 } 141 142 143 public static SockJsFrame openFrame() { 144 return OPEN_FRAME; 145 } 146 147 public static SockJsFrame heartbeatFrame() { 148 return HEARTBEAT_FRAME; 149 } 150 151 public static SockJsFrame messageFrame(SockJsMessageCodec codec, String... messages) { 152 String encoded = codec.encode(messages); 153 return new SockJsFrame(encoded); 154 } 155 156 public static SockJsFrame closeFrameGoAway() { 157 return CLOSE_GO_AWAY_FRAME; 158 } 159 160 public static SockJsFrame closeFrameAnotherConnectionOpen() { 161 return CLOSE_ANOTHER_CONNECTION_OPEN_FRAME; 162 } 163 164 public static SockJsFrame closeFrame(int code, String reason) { 165 return new SockJsFrame("c[" + code + ",\"" + reason + "\"]"); 166 } 167 168}