Leaderboard dynamic & Lock System & Refractor WebSocket + Headers

This commit is contained in:
jeffcheasey88 2023-04-10 22:46:18 +02:00
parent 14e37b9e38
commit 9ba9ab12c7
14 changed files with 297 additions and 141 deletions

View file

@ -5,6 +5,8 @@ import static be.jeffcheasey88.peeratcode.framework.RequestType.OPTIONS;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import javax.net.ssl.SSLServerSocket;
@ -18,10 +20,12 @@ import be.jeffcheasey88.peeratcode.framework.Response;
import be.jeffcheasey88.peeratcode.framework.Route;
import be.jeffcheasey88.peeratcode.framework.Router;
import be.jeffcheasey88.peeratcode.framework.User;
import be.jeffcheasey88.peeratcode.model.Completion;
import be.jeffcheasey88.peeratcode.repository.DatabaseRepository;
import be.jeffcheasey88.peeratcode.routes.BadgeDetails;
import be.jeffcheasey88.peeratcode.routes.ChapterElement;
import be.jeffcheasey88.peeratcode.routes.ChapterList;
import be.jeffcheasey88.peeratcode.routes.DynamicLeaderboard;
import be.jeffcheasey88.peeratcode.routes.Leaderboard;
import be.jeffcheasey88.peeratcode.routes.Login;
import be.jeffcheasey88.peeratcode.routes.PlayerDetails;
@ -29,7 +33,7 @@ import be.jeffcheasey88.peeratcode.routes.PuzzleElement;
import be.jeffcheasey88.peeratcode.routes.PuzzleResponse;
import be.jeffcheasey88.peeratcode.routes.Register;
import be.jeffcheasey88.peeratcode.routes.Result;
import be.jeffcheasey88.peeratcode.routes.groups.CreateGroup;
import be.jeffcheasey88.peeratcode.routes.groups.GroupCreate;
import be.jeffcheasey88.peeratcode.routes.groups.GroupJoin;
import be.jeffcheasey88.peeratcode.routes.groups.GroupList;
import be.jeffcheasey88.peeratcode.routes.groups.GroupQuit;
@ -75,15 +79,19 @@ public class Main{
router.register(new Register(router.getDataBase(), router, config.getUsersFiles()));
router.register(new Login(router.getDataBase(), router));
router.register(new Result(router.getDataBase()));
router.register(new PuzzleResponse(router.getDataBase(), config.getUsersFiles()));
router.register(new Leaderboard(router.getDataBase()));
router.register(new PlayerDetails(router.getDataBase()));
router.register(new BadgeDetails(router.getDataBase()));
router.register(new GroupList(router.getDataBase()));
router.register(new CreateGroup(router.getDataBase()));
router.register(new GroupCreate(router.getDataBase()));
router.register(new GroupJoin(router.getDataBase()));
router.register(new GroupQuit(router.getDataBase()));
DynamicLeaderboard dlb = new DynamicLeaderboard(router.getDataBase());
router.register(dlb);
router.register(new PuzzleResponse(router.getDataBase(), config.getUsersFiles(), dlb.getLocker()));
}
private static void startWebServer(Configuration config, Router router) throws IOException {

View file

@ -27,6 +27,7 @@ public class Client extends Thread{
try {
String[] headers = reader.readLine().split("\\s");
System.out.println(Arrays.toString(headers));
reader.readHeaders();
router.exec(RequestType.valueOf(headers[0]), headers[1], isLogin(reader), reader, writer);
writer.flush();
@ -37,8 +38,9 @@ public class Client extends Thread{
}
private User isLogin(HttpReader reader) throws Exception{
String auth = HttpUtil.readAuthorization(reader);
String auth = reader.getHeader("Authorization");
if(auth == null) return null;
auth = auth.substring(7);
try{
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime()

View file

@ -5,23 +5,51 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpReader {
private static Pattern HEADER_PATTERN = Pattern.compile("^([^:]*):\\s+(.*)$");
private Socket socket;
private InputStream in;
private BufferedReader reader;
public HttpReader(Socket socket) throws Exception {
private Map<String, String> headers;
public HttpReader(Socket socket) throws Exception{
this.socket = socket;
this.in = socket.getInputStream();
this.reader = new BufferedReader(new InputStreamReader(in));
this.headers = new HashMap<>();
}
public HttpReader(HttpReader origin) throws Exception{
this.socket = origin.socket;
this.in = origin.in;
this.reader = origin.reader;
}
public boolean isClosed() {
return this.socket.isClosed();
}
public void readHeaders() throws Exception{
String line;
while(((line = reader.readLine()) != null) && (line.length() > 0)){
Matcher matcher = HEADER_PATTERN.matcher(line);
matcher.matches();
this.headers.put(matcher.group(1), matcher.group(2));
}
}
public String getHeader(String key){
return this.headers.get(key);
}
public int read(byte[] buffer) throws IOException {
return this.in.read(buffer);
}

View file

@ -1,5 +1,6 @@
package be.jeffcheasey88.peeratcode.framework;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
@ -45,7 +46,7 @@ public class HttpUtil{
}
public static void switchToWebSocket(HttpReader reader, HttpWriter writer) throws Exception{
String key = readWebSocketKey(reader);
String key = reader.getHeader("Sec-WebSocket-Key");
if (key == null) throw new IllegalArgumentException();
writer.write("HTTP/1.1 101 Switching Protocols\n");
@ -57,34 +58,6 @@ public class HttpUtil{
writer.flush();
}
private static Pattern WEBSOCKET_KEY = Pattern.compile("Sec-WebSocket-Key: (.*)");
public static String readWebSocketKey(HttpReader reader) throws Exception{
String line;
String key = null;
while(((line = reader.readLine()) != null) && (line.length() > 0)){
if(key != null) continue;
Matcher matcher = WEBSOCKET_KEY.matcher(line);
if(matcher.matches()) key = matcher.group(1);
}
return key;
}
private static Pattern AUTORIZATION = Pattern.compile("Authorization: Bearer (.*)");
public static String readAuthorization(HttpReader reader) throws Exception{
String line;
String key = null;
while (((line = reader.readLine()) != null) && (line.length() > 0)){
Matcher matcher = AUTORIZATION.matcher(line);
if(matcher.matches()){
key = matcher.group(1);
break;
}
}
return key;
}
public static Object readJson(HttpReader reader) throws Exception{
String line = "";
while (reader.ready()){
@ -103,104 +76,6 @@ public class HttpUtil{
return null;
}
// I found this code on StackOverFlow !!!!! (and the write too)
public static String readWebSocket(HttpReader reader) throws Exception{
int buffLenth = 1024;
int len = 0;
byte[] b = new byte[buffLenth];
// rawIn is a Socket.getInputStream();
while (true){
len = reader.read(b);
if (len != -1){
byte rLength = 0;
int rMaskIndex = 2;
int rDataStart = 0;
// b[0] is always text in my case so no need to check;
byte data = b[1];
byte op = (byte) 127;
rLength = (byte) (data & op);
if (rLength == (byte) 126)
rMaskIndex = 4;
if (rLength == (byte) 127)
rMaskIndex = 10;
byte[] masks = new byte[4];
int j = 0;
int i = 0;
for (i = rMaskIndex; i < (rMaskIndex + 4); i++){
masks[j] = b[i];
j++;
}
rDataStart = rMaskIndex + 4;
int messLen = len - rDataStart;
byte[] message = new byte[messLen];
for (i = rDataStart, j = 0; i < len; i++, j++){
message[j] = (byte) (b[i] ^ masks[j % 4]);
}
return new String(message);
} else
break;
}
return null;
}
public static void sendWebSocket(HttpWriter writer, String message) throws Exception{
byte[] rawData = message.getBytes();
int frameCount = 0;
byte[] frame = new byte[10];
frame[0] = (byte) 129;
if (rawData.length <= 125){
frame[1] = (byte) rawData.length;
frameCount = 2;
} else if (rawData.length >= 126 && rawData.length <= 65535){
frame[1] = (byte) 126;
int len = rawData.length;
frame[2] = (byte) ((len >> 8) & (byte) 255);
frame[3] = (byte) (len & (byte) 255);
frameCount = 4;
} else {
frame[1] = (byte) 127;
int len = rawData.length;
frame[2] = (byte) ((len >> 56) & (byte) 255);
frame[3] = (byte) ((len >> 48) & (byte) 255);
frame[4] = (byte) ((len >> 40) & (byte) 255);
frame[5] = (byte) ((len >> 32) & (byte) 255);
frame[6] = (byte) ((len >> 24) & (byte) 255);
frame[7] = (byte) ((len >> 16) & (byte) 255);
frame[8] = (byte) ((len >> 8) & (byte) 255);
frame[9] = (byte) (len & (byte) 255);
frameCount = 10;
}
int bLength = frameCount + rawData.length;
byte[] reply = new byte[bLength];
int bLim = 0;
for (int i = 0; i < frameCount; i++){
reply[bLim] = frame[i];
bLim++;
}
for (int i = 0; i < rawData.length; i++){
reply[bLim] = rawData[i];
bLim++;
}
writer.write(reply);
writer.flush();
}
private static String codeMessage(int paramInt){
switch (paramInt){
case 200:

View file

@ -17,6 +17,11 @@ public class HttpWriter{
this.writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
}
public HttpWriter(HttpWriter origin) throws Exception{
this.out = origin.out;
this.writer = origin.writer;
}
public void write(byte[] buffer) throws IOException{
this.out.write(buffer);
this.out.flush();

View file

@ -0,0 +1,59 @@
package be.jeffcheasey88.peeratcode.framework;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class Locker<V>{
private Map<Key, BlockingQueue<V>> map;
public Locker(){
this.map = new HashMap<>();
}
public void init(Key key){
this.map.put(key, new LinkedBlockingQueue<>());
}
public void remove(Key key){
this.map.remove(key);
}
private BlockingQueue<V> get(Key key){
return this.map.get(key);
}
public void setValue(V value){
for(Entry<Key, BlockingQueue<V>> entry : this.map.entrySet()){
entry.getValue().add(value);
this.unlock(entry.getKey());
}
}
public V getValue(Key key){
BlockingQueue<V> queue = get(key);
if(queue.isEmpty()) return null;
return queue.poll();
}
public void lock(Key key) throws InterruptedException{
BlockingQueue<V> queue = get(key);
if(queue.isEmpty()){
synchronized(queue){
queue.wait();
}
}
}
public void unlock(Key key){
BlockingQueue<V> queue = get(key);
synchronized(queue){
queue.notify();
}
}
public static class Key{ public Key(){} }
}

View file

@ -15,4 +15,5 @@ public @interface Route{
boolean needLogin() default false;
boolean websocket() default false;
}

View file

@ -63,6 +63,11 @@ public class Router{
Matcher matcher = this.patterns.get(routes.getKey()).matcher(path);
if(matcher.matches()){
if(user == null && routes.getValue().needLogin()) return;
if(routes.getValue().websocket()){
HttpUtil.switchToWebSocket(reader, writer);
reader = new WebSocketReader(reader);
writer = new WebSocketWriter(writer);
}
routes.getKey().exec(matcher, user, reader, writer);
return;
}

View file

@ -0,0 +1,62 @@
package be.jeffcheasey88.peeratcode.framework;
import java.io.IOException;
public class WebSocketReader extends HttpReader{
public WebSocketReader(HttpReader origin) throws Exception {
super(origin);
}
@Override
public String readLine() throws IOException{
//read websocket found on StackOverFlow
int buffLenth = 1024;
int len = 0;
byte[] b = new byte[buffLenth];
// rawIn is a Socket.getInputStream();
while (true){
len = read(b);
if (len != -1){
byte rLength = 0;
int rMaskIndex = 2;
int rDataStart = 0;
// b[0] is always text in my case so no need to check;
byte data = b[1];
byte op = (byte) 127;
rLength = (byte) (data & op);
if (rLength == (byte) 126)
rMaskIndex = 4;
if (rLength == (byte) 127)
rMaskIndex = 10;
byte[] masks = new byte[4];
int j = 0;
int i = 0;
for (i = rMaskIndex; i < (rMaskIndex + 4); i++){
masks[j] = b[i];
j++;
}
rDataStart = rMaskIndex + 4;
int messLen = len - rDataStart;
byte[] message = new byte[messLen];
for (i = rDataStart, j = 0; i < len; i++, j++){
message[j] = (byte) (b[i] ^ masks[j % 4]);
}
return new String(message);
} else
break;
}
return null;
}
}

View file

@ -0,0 +1,61 @@
package be.jeffcheasey88.peeratcode.framework;
import java.io.IOException;
public class WebSocketWriter extends HttpWriter{
public WebSocketWriter(HttpWriter origin) throws Exception {
super(origin);
}
@Override
public void write(String message) throws IOException{
//write websocket found on StackOverFlow
byte[] rawData = message.getBytes();
int frameCount = 0;
byte[] frame = new byte[10];
frame[0] = (byte) 129;
if (rawData.length <= 125){
frame[1] = (byte) rawData.length;
frameCount = 2;
} else if (rawData.length >= 126 && rawData.length <= 65535){
frame[1] = (byte) 126;
int len = rawData.length;
frame[2] = (byte) ((len >> 8) & (byte) 255);
frame[3] = (byte) (len & (byte) 255);
frameCount = 4;
} else {
frame[1] = (byte) 127;
int len = rawData.length;
frame[2] = (byte) ((len >> 56) & (byte) 255);
frame[3] = (byte) ((len >> 48) & (byte) 255);
frame[4] = (byte) ((len >> 40) & (byte) 255);
frame[5] = (byte) ((len >> 32) & (byte) 255);
frame[6] = (byte) ((len >> 24) & (byte) 255);
frame[7] = (byte) ((len >> 16) & (byte) 255);
frame[8] = (byte) ((len >> 8) & (byte) 255);
frame[9] = (byte) (len & (byte) 255);
frameCount = 10;
}
int bLength = frameCount + rawData.length;
byte[] reply = new byte[bLength];
int bLim = 0;
for (int i = 0; i < frameCount; i++){
reply[bLim] = frame[i];
bLim++;
}
for (int i = 0; i < rawData.length; i++){
reply[bLim] = rawData[i];
bLim++;
}
write(reply);
flush();
}
}

View file

@ -0,0 +1,43 @@
package be.jeffcheasey88.peeratcode.routes;
import java.util.regex.Matcher;
import be.jeffcheasey88.peeratcode.framework.HttpReader;
import be.jeffcheasey88.peeratcode.framework.HttpWriter;
import be.jeffcheasey88.peeratcode.framework.Locker;
import be.jeffcheasey88.peeratcode.framework.Locker.Key;
import be.jeffcheasey88.peeratcode.framework.Route;
import be.jeffcheasey88.peeratcode.framework.User;
import be.jeffcheasey88.peeratcode.model.Completion;
import be.jeffcheasey88.peeratcode.repository.DatabaseRepository;
public class DynamicLeaderboard extends Leaderboard{
private Locker<Completion> locker;
public DynamicLeaderboard(DatabaseRepository databaseRepo){
super(databaseRepo);
this.locker = new Locker<>();
}
public Locker<Completion> getLocker(){
return this.locker;
}
@Route(path = "^\\/rleaderboard\\/?(\\d+)?$", websocket = true)
public void exec(Matcher matcher, User user, HttpReader reader, HttpWriter writer) throws Exception{
Key key = new Key();
locker.init(key);
while(!reader.isClosed()){
locker.getValue(key);
if(matcher.group(1) != null){
groupsLeaderboard(Integer.parseInt(matcher.group(1)), writer);
}else{
playersLeaderboard(writer);
}
locker.lock(key);
}
locker.remove(key);
}
}

View file

@ -37,7 +37,7 @@ public class Leaderboard implements Response {
}
}
private void groupsLeaderboard(int chapterId, HttpWriter writer) throws IOException {
public final void groupsLeaderboard(int chapterId, HttpWriter writer) throws IOException {
Chapter chInfo = databaseRepo.getChapter(chapterId);
SortedSet<Group> allGroupsForChapter = databaseRepo.getAllGroupForChapterLeaderboard(chapterId);
@ -68,7 +68,7 @@ public class Leaderboard implements Response {
writer.write(leaderboardJSON.toJSONString().replace("\\", ""));
}
private void playersLeaderboard(HttpWriter writer) throws IOException {
public final void playersLeaderboard(HttpWriter writer) throws IOException {
SortedSet<Player> allPlayers = databaseRepo.getAllPlayerForLeaderboard();
JSONArray playersJSON = new JSONArray();
if (allPlayers != null) {
@ -77,8 +77,8 @@ public class Leaderboard implements Response {
playerJSON.put("pseudo", player.getPseudo());
if (player.getGroups() != null)
playerJSON.put("groups", player.getJsonGroups());
if (player.getAvatar() != null)
playerJSON.put("avatar", Base64.getEncoder().encodeToString(player.getAvatar()));
// if (player.getAvatar() != null)
// playerJSON.put("avatar", Base64.getEncoder().encodeToString(player.getAvatar()));
playerJSON.put("rank", player.getRank());
playerJSON.put("score", player.getTotalScore());
playerJSON.put("completions", player.getTotalCompletion());

View file

@ -14,6 +14,7 @@ import org.json.simple.JSONObject;
import be.jeffcheasey88.peeratcode.framework.HttpReader;
import be.jeffcheasey88.peeratcode.framework.HttpUtil;
import be.jeffcheasey88.peeratcode.framework.HttpWriter;
import be.jeffcheasey88.peeratcode.framework.Locker;
import be.jeffcheasey88.peeratcode.framework.Response;
import be.jeffcheasey88.peeratcode.framework.Route;
import be.jeffcheasey88.peeratcode.framework.User;
@ -26,9 +27,12 @@ public class PuzzleResponse implements Response {
private final DatabaseRepository databaseRepo;
private final String usersFilesPath;
public PuzzleResponse(DatabaseRepository databaseRepo, String initUsersFilesPath) {
private final Locker<Completion> leaderboard;
public PuzzleResponse(DatabaseRepository databaseRepo, String initUsersFilesPath, Locker<Completion> locker){
this.databaseRepo = databaseRepo;
usersFilesPath = initUsersFilesPath;
this.leaderboard = locker;
}
@Route(path = "^\\/puzzleResponse\\/([0-9]+)$", type = POST, needLogin = true)
@ -54,6 +58,9 @@ public class PuzzleResponse implements Response {
responseJSON.put("tries", completion.getTries());
}
writer.write(responseJSON.toJSONString());
writer.flush();
leaderboard.setValue(completion);
}
private void saveSourceCode(ReceivedResponse received, Player player){

View file

@ -15,11 +15,11 @@ import be.jeffcheasey88.peeratcode.framework.User;
import be.jeffcheasey88.peeratcode.model.Group;
import be.jeffcheasey88.peeratcode.repository.DatabaseRepository;
public class CreateGroup implements Response {
public class GroupCreate implements Response {
private DatabaseRepository repo;
public CreateGroup(DatabaseRepository repo) {
public GroupCreate(DatabaseRepository repo) {
this.repo = repo;
}