You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-20 15:36:17 +00:00
🆙 As NAcho wants it, add effect on disconnected user & small security update
This commit is contained in:
+59
-37
@@ -1,23 +1,16 @@
|
|||||||
package com.eu.habbo.habbohotel.gameclients;
|
package com.eu.habbo.habbohotel.gameclients;
|
||||||
|
|
||||||
import com.eu.habbo.Emulator;
|
import com.eu.habbo.Emulator;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.Room;
|
||||||
|
import com.eu.habbo.habbohotel.rooms.RoomUnit;
|
||||||
import com.eu.habbo.habbohotel.users.Habbo;
|
import com.eu.habbo.habbohotel.users.Habbo;
|
||||||
|
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages a grace period for disconnected users. Instead of immediately
|
|
||||||
* disposing a Habbo when their WebSocket drops, the Habbo is held in
|
|
||||||
* a "ghost" state for a configurable number of seconds. If the same
|
|
||||||
* user reconnects (via SSO ticket) within the grace window, their
|
|
||||||
* existing Habbo object is resumed on the new connection — keeping
|
|
||||||
* them in their room, preserving inventory state, etc.
|
|
||||||
*
|
|
||||||
* Config key: session.reconnect.grace.seconds (default: 30)
|
|
||||||
*/
|
|
||||||
public class SessionResumeManager {
|
public class SessionResumeManager {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class);
|
||||||
@@ -37,12 +30,10 @@ public class SessionResumeManager {
|
|||||||
return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30);
|
return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public int getPausedEffectId() {
|
||||||
* Park a disconnected Habbo in ghost mode. Their room presence is
|
return Emulator.getConfig().getInt("session.reconnect.effect.id", 170);
|
||||||
* preserved, but the old GameClient channel is closed.
|
}
|
||||||
*
|
|
||||||
* @return true if the habbo was parked (grace period > 0), false if immediate dispose should happen
|
|
||||||
*/
|
|
||||||
public boolean parkHabbo(Habbo habbo, String ssoTicket) {
|
public boolean parkHabbo(Habbo habbo, String ssoTicket) {
|
||||||
int graceSeconds = getGracePeriodSeconds();
|
int graceSeconds = getGracePeriodSeconds();
|
||||||
if (graceSeconds <= 0) {
|
if (graceSeconds <= 0) {
|
||||||
@@ -51,7 +42,6 @@ public class SessionResumeManager {
|
|||||||
|
|
||||||
int userId = habbo.getHabboInfo().getId();
|
int userId = habbo.getHabboInfo().getId();
|
||||||
|
|
||||||
// Cancel any existing ghost session for this user
|
|
||||||
GhostSession existing = ghostSessions.remove(userId);
|
GhostSession existing = ghostSessions.remove(userId);
|
||||||
if (existing != null && existing.disposeFuture != null) {
|
if (existing != null && existing.disposeFuture != null) {
|
||||||
existing.disposeFuture.cancel(false);
|
existing.disposeFuture.cancel(false);
|
||||||
@@ -60,12 +50,18 @@ public class SessionResumeManager {
|
|||||||
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
|
LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period",
|
||||||
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
|
habbo.getHabboInfo().getUsername(), userId, graceSeconds);
|
||||||
|
|
||||||
// Restore the SSO ticket so the client can reconnect with the same ticket
|
|
||||||
if (ssoTicket != null && !ssoTicket.isEmpty()) {
|
if (ssoTicket != null && !ssoTicket.isEmpty()) {
|
||||||
restoreSsoTicket(userId, ssoTicket);
|
restoreSsoTicket(userId, ssoTicket);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schedule the final disconnect after the grace period
|
int previousEffectId = 0;
|
||||||
|
int previousEffectEnd = 0;
|
||||||
|
RoomUnit unit = habbo.getRoomUnit();
|
||||||
|
if (unit != null) {
|
||||||
|
previousEffectId = unit.getEffectId();
|
||||||
|
previousEffectEnd = unit.getEffectEndTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
|
ScheduledFuture<?> future = Emulator.getThreading().run(() -> {
|
||||||
GhostSession ghost = ghostSessions.remove(userId);
|
GhostSession ghost = ghostSessions.remove(userId);
|
||||||
if (ghost != null) {
|
if (ghost != null) {
|
||||||
@@ -75,22 +71,19 @@ public class SessionResumeManager {
|
|||||||
}
|
}
|
||||||
}, graceSeconds * 1000);
|
}, graceSeconds * 1000);
|
||||||
|
|
||||||
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future));
|
ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future, previousEffectId, previousEffectEnd));
|
||||||
|
|
||||||
|
applyPausedEffect(habbo);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to resume a ghost session for the given user ID.
|
|
||||||
*
|
|
||||||
* @return the parked Habbo if found within grace period, null otherwise
|
|
||||||
*/
|
|
||||||
public Habbo resumeSession(int userId) {
|
public Habbo resumeSession(int userId) {
|
||||||
GhostSession ghost = ghostSessions.remove(userId);
|
GhostSession ghost = ghostSessions.remove(userId);
|
||||||
if (ghost == null) {
|
if (ghost == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel the scheduled dispose
|
|
||||||
if (ghost.disposeFuture != null) {
|
if (ghost.disposeFuture != null) {
|
||||||
ghost.disposeFuture.cancel(false);
|
ghost.disposeFuture.cancel(false);
|
||||||
}
|
}
|
||||||
@@ -98,19 +91,15 @@ public class SessionResumeManager {
|
|||||||
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
|
LOGGER.info("[SessionResume] Resuming session for {} (id={})",
|
||||||
ghost.habbo.getHabboInfo().getUsername(), userId);
|
ghost.habbo.getHabboInfo().getUsername(), userId);
|
||||||
|
|
||||||
|
restorePausedEffect(ghost);
|
||||||
|
|
||||||
return ghost.habbo;
|
return ghost.habbo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a user has a ghost session (is in grace period).
|
|
||||||
*/
|
|
||||||
public boolean hasGhostSession(int userId) {
|
public boolean hasGhostSession(int userId) {
|
||||||
return ghostSessions.containsKey(userId);
|
return ghostSessions.containsKey(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Immediately expire all ghost sessions (e.g. on emulator shutdown).
|
|
||||||
*/
|
|
||||||
public void disposeAll() {
|
public void disposeAll() {
|
||||||
for (GhostSession ghost : ghostSessions.values()) {
|
for (GhostSession ghost : ghostSessions.values()) {
|
||||||
if (ghost.disposeFuture != null) {
|
if (ghost.disposeFuture != null) {
|
||||||
@@ -121,9 +110,6 @@ public class SessionResumeManager {
|
|||||||
ghostSessions.clear();
|
ghostSessions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform the actual full disconnect that normally happens in Habbo.disconnect().
|
|
||||||
*/
|
|
||||||
private void performFullDisconnect(Habbo habbo) {
|
private void performFullDisconnect(Habbo habbo) {
|
||||||
try {
|
try {
|
||||||
habbo.getHabboInfo().setOnline(false);
|
habbo.getHabboInfo().setOnline(false);
|
||||||
@@ -132,7 +118,6 @@ public class SessionResumeManager {
|
|||||||
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
LOGGER.error("[SessionResume] Error during deferred disconnect", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the SSO ticket now that the grace period is truly over
|
|
||||||
clearSsoTicket(habbo.getHabboInfo().getId());
|
clearSsoTicket(habbo.getHabboInfo().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,6 +133,38 @@ public class SessionResumeManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyPausedEffect(Habbo habbo) {
|
||||||
|
int effectId = getPausedEffectId();
|
||||||
|
if (effectId <= 0) return;
|
||||||
|
try {
|
||||||
|
RoomUnit unit = habbo.getRoomUnit();
|
||||||
|
Room room = habbo.getHabboInfo() == null ? null : habbo.getHabboInfo().getCurrentRoom();
|
||||||
|
if (unit == null || room == null) return;
|
||||||
|
int endTimestamp = Emulator.getIntUnixTimestamp() + getGracePeriodSeconds() + 10;
|
||||||
|
unit.setEffectId(effectId, endTimestamp);
|
||||||
|
room.sendComposer(new RoomUserEffectComposer(unit).compose());
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("[SessionResume] Failed to apply paused effect", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restorePausedEffect(GhostSession ghost) {
|
||||||
|
try {
|
||||||
|
Habbo habbo = ghost.habbo;
|
||||||
|
RoomUnit unit = habbo.getRoomUnit();
|
||||||
|
Room room = habbo.getHabboInfo() == null ? null : habbo.getHabboInfo().getCurrentRoom();
|
||||||
|
if (unit == null || room == null) return;
|
||||||
|
|
||||||
|
int pausedEffectId = getPausedEffectId();
|
||||||
|
if (unit.getEffectId() == pausedEffectId) {
|
||||||
|
unit.setEffectId(ghost.previousEffectId, ghost.previousEffectEnd);
|
||||||
|
room.sendComposer(new RoomUserEffectComposer(unit).compose());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("[SessionResume] Failed to restore previous effect", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void clearSsoTicket(int userId) {
|
private void clearSsoTicket(int userId) {
|
||||||
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (var connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) {
|
||||||
@@ -163,11 +180,16 @@ public class SessionResumeManager {
|
|||||||
final Habbo habbo;
|
final Habbo habbo;
|
||||||
final String ssoTicket;
|
final String ssoTicket;
|
||||||
final ScheduledFuture<?> disposeFuture;
|
final ScheduledFuture<?> disposeFuture;
|
||||||
|
final int previousEffectId;
|
||||||
|
final int previousEffectEnd;
|
||||||
|
|
||||||
GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture) {
|
GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture<?> disposeFuture,
|
||||||
|
int previousEffectId, int previousEffectEnd) {
|
||||||
this.habbo = habbo;
|
this.habbo = habbo;
|
||||||
this.ssoTicket = ssoTicket;
|
this.ssoTicket = ssoTicket;
|
||||||
this.disposeFuture = disposeFuture;
|
this.disposeFuture = disposeFuture;
|
||||||
|
this.previousEffectId = previousEffectId;
|
||||||
|
this.previousEffectEnd = previousEffectEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
@@ -54,6 +54,7 @@ public class CustomBadgeManager {
|
|||||||
private final SecureRandom random = new SecureRandom();
|
private final SecureRandom random = new SecureRandom();
|
||||||
private final Map<Integer, long[]> rateBuckets = new ConcurrentHashMap<>();
|
private final Map<Integer, long[]> rateBuckets = new ConcurrentHashMap<>();
|
||||||
private final Map<String, BadgeText> textCache = new ConcurrentHashMap<>();
|
private final Map<String, BadgeText> textCache = new ConcurrentHashMap<>();
|
||||||
|
private final java.util.concurrent.atomic.AtomicLong textCacheVersion = new java.util.concurrent.atomic.AtomicLong();
|
||||||
|
|
||||||
private volatile CustomBadgeSettings settings;
|
private volatile CustomBadgeSettings settings;
|
||||||
|
|
||||||
@@ -74,6 +75,10 @@ public class CustomBadgeManager {
|
|||||||
return java.util.Collections.unmodifiableMap(this.textCache);
|
return java.util.Collections.unmodifiableMap(this.textCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTextCacheVersion() {
|
||||||
|
return this.textCacheVersion.get();
|
||||||
|
}
|
||||||
|
|
||||||
private void loadTextCache() {
|
private void loadTextCache() {
|
||||||
Map<String, BadgeText> next = new java.util.HashMap<>();
|
Map<String, BadgeText> next = new java.util.HashMap<>();
|
||||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||||
@@ -93,6 +98,7 @@ public class CustomBadgeManager {
|
|||||||
}
|
}
|
||||||
this.textCache.clear();
|
this.textCache.clear();
|
||||||
this.textCache.putAll(next);
|
this.textCache.putAll(next);
|
||||||
|
this.textCacheVersion.incrementAndGet();
|
||||||
LOGGER.info("CustomBadgeManager -> loaded {} custom badge texts into memory.", next.size());
|
LOGGER.info("CustomBadgeManager -> loaded {} custom badge texts into memory.", next.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,6 +225,7 @@ public class CustomBadgeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.textCache.put(badgeId, new BadgeText(safeName, safeDesc));
|
this.textCache.put(badgeId, new BadgeText(safeName, safeDesc));
|
||||||
|
this.textCacheVersion.incrementAndGet();
|
||||||
issueBadgeToInventory(userId, badgeId);
|
issueBadgeToInventory(userId, badgeId);
|
||||||
|
|
||||||
return new CustomBadge(generatedId, userId, badgeId, safeName, safeDesc, now, now);
|
return new CustomBadge(generatedId, userId, badgeId, safeName, safeDesc, now, now);
|
||||||
@@ -264,6 +271,7 @@ public class CustomBadgeManager {
|
|||||||
String safeDesc = sanitize(description, 255);
|
String safeDesc = sanitize(description, 255);
|
||||||
this.textCache.remove(oldBadgeId);
|
this.textCache.remove(oldBadgeId);
|
||||||
this.textCache.put(newBadgeId, new BadgeText(safeName, safeDesc));
|
this.textCache.put(newBadgeId, new BadgeText(safeName, safeDesc));
|
||||||
|
this.textCacheVersion.incrementAndGet();
|
||||||
renameBadgeInInventory(userId, oldBadgeId, newBadgeId);
|
renameBadgeInInventory(userId, oldBadgeId, newBadgeId);
|
||||||
deleteBadgeFileQuietly(oldBadgeId);
|
deleteBadgeFileQuietly(oldBadgeId);
|
||||||
return new CustomBadge(existing.getId(), userId, newBadgeId, safeName, safeDesc, existing.getDateCreated(), now);
|
return new CustomBadge(existing.getId(), userId, newBadgeId, safeName, safeDesc, existing.getDateCreated(), now);
|
||||||
@@ -288,6 +296,7 @@ public class CustomBadgeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.textCache.remove(badgeId);
|
this.textCache.remove(badgeId);
|
||||||
|
this.textCacheVersion.incrementAndGet();
|
||||||
revokeBadgeFromInventory(userId, badgeId);
|
revokeBadgeFromInventory(userId, badgeId);
|
||||||
deleteBadgeFileQuietly(badgeId);
|
deleteBadgeFileQuietly(badgeId);
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-13
@@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.users.custombadge.CustomBadgeException;
|
|||||||
import com.eu.habbo.habbohotel.users.custombadge.CustomBadgeManager;
|
import com.eu.habbo.habbohotel.users.custombadge.CustomBadgeManager;
|
||||||
import com.eu.habbo.networking.gameserver.GameServerAttributes;
|
import com.eu.habbo.networking.gameserver.GameServerAttributes;
|
||||||
import com.eu.habbo.networking.gameserver.auth.AccessTokenService;
|
import com.eu.habbo.networking.gameserver.auth.AccessTokenService;
|
||||||
|
import com.eu.habbo.networking.gameserver.auth.AuthRateLimiter;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
@@ -30,6 +31,9 @@ public class BadgeHttpHandler extends ChannelInboundHandlerAdapter {
|
|||||||
private static final String BASE_PATH = "/api/badges/custom";
|
private static final String BASE_PATH = "/api/badges/custom";
|
||||||
private static final int MAX_BODY_BYTES = 128 * 1024;
|
private static final int MAX_BODY_BYTES = 128 * 1024;
|
||||||
|
|
||||||
|
private static volatile JsonObject cachedTextsResponse = null;
|
||||||
|
private static volatile long cachedTextsVersion = -1L;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
if (!(msg instanceof FullHttpRequest req)) {
|
if (!(msg instanceof FullHttpRequest req)) {
|
||||||
@@ -58,6 +62,13 @@ public class BadgeHttpHandler extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
if (path.equals(BASE_PATH + "/texts")) {
|
if (path.equals(BASE_PATH + "/texts")) {
|
||||||
if (req.method() == HttpMethod.GET || req.method() == HttpMethod.HEAD) {
|
if (req.method() == HttpMethod.GET || req.method() == HttpMethod.HEAD) {
|
||||||
|
String ip = resolveClientIp(ctx, req);
|
||||||
|
if (!AuthRateLimiter.tryProbe(ip)) {
|
||||||
|
long secs = AuthRateLimiter.secondsUntilProbeReset(ip);
|
||||||
|
sendJson(ctx, req, HttpResponseStatus.TOO_MANY_REQUESTS,
|
||||||
|
error("Too many requests. Try again in " + secs + "s."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
handleTexts(ctx, req);
|
handleTexts(ctx, req);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -116,20 +127,26 @@ public class BadgeHttpHandler extends ChannelInboundHandlerAdapter {
|
|||||||
|
|
||||||
private void handleTexts(ChannelHandlerContext ctx, FullHttpRequest req) {
|
private void handleTexts(ChannelHandlerContext ctx, FullHttpRequest req) {
|
||||||
CustomBadgeManager manager = Emulator.getGameEnvironment().getCustomBadgeManager();
|
CustomBadgeManager manager = Emulator.getGameEnvironment().getCustomBadgeManager();
|
||||||
java.util.Map<String, CustomBadgeManager.BadgeText> cache = manager.getTextCache();
|
long version = manager.getTextCacheVersion();
|
||||||
|
JsonObject ok = cachedTextsResponse;
|
||||||
JsonObject texts = new JsonObject();
|
if (ok == null || cachedTextsVersion != version) {
|
||||||
for (java.util.Map.Entry<String, CustomBadgeManager.BadgeText> entry : cache.entrySet()) {
|
java.util.Map<String, CustomBadgeManager.BadgeText> cache = manager.getTextCache();
|
||||||
String badgeId = entry.getKey();
|
JsonObject texts = new JsonObject();
|
||||||
CustomBadgeManager.BadgeText value = entry.getValue();
|
for (java.util.Map.Entry<String, CustomBadgeManager.BadgeText> entry : cache.entrySet()) {
|
||||||
texts.addProperty("badge_name_" + badgeId, value.name);
|
String badgeId = entry.getKey();
|
||||||
texts.addProperty("badge_desc_" + badgeId, value.description);
|
CustomBadgeManager.BadgeText value = entry.getValue();
|
||||||
|
texts.addProperty("badge_name_" + badgeId, value.name);
|
||||||
|
texts.addProperty("badge_desc_" + badgeId, value.description);
|
||||||
|
}
|
||||||
|
JsonObject built = new JsonObject();
|
||||||
|
built.add("texts", texts);
|
||||||
|
built.addProperty("count", cache.size());
|
||||||
|
built.addProperty("version", version);
|
||||||
|
cachedTextsResponse = built;
|
||||||
|
cachedTextsVersion = version;
|
||||||
|
ok = built;
|
||||||
}
|
}
|
||||||
|
sendJsonCached(ctx, req, HttpResponseStatus.OK, ok);
|
||||||
JsonObject ok = new JsonObject();
|
|
||||||
ok.add("texts", texts);
|
|
||||||
ok.addProperty("count", cache.size());
|
|
||||||
sendJson(ctx, req, HttpResponseStatus.OK, ok);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleList(ChannelHandlerContext ctx, FullHttpRequest req, int userId) {
|
private void handleList(ChannelHandlerContext ctx, FullHttpRequest req, int userId) {
|
||||||
@@ -287,6 +304,21 @@ public class BadgeHttpHandler extends ChannelInboundHandlerAdapter {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void sendJsonCached(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||||
|
HttpResponseStatus status, JsonObject body) {
|
||||||
|
byte[] bytes = body.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||||
|
HttpVersion.HTTP_1_1, status, Unpooled.wrappedBuffer(bytes));
|
||||||
|
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
|
||||||
|
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
|
||||||
|
response.headers().set(HttpHeaderNames.CACHE_CONTROL, "public, max-age=30");
|
||||||
|
applyCors(req, response);
|
||||||
|
boolean keepAlive = isKeepAlive(req);
|
||||||
|
if (keepAlive) response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
|
||||||
|
var future = ctx.writeAndFlush(response);
|
||||||
|
if (!keepAlive) future.addListener(ChannelFutureListener.CLOSE);
|
||||||
|
}
|
||||||
|
|
||||||
private static void sendJson(ChannelHandlerContext ctx, FullHttpRequest req,
|
private static void sendJson(ChannelHandlerContext ctx, FullHttpRequest req,
|
||||||
HttpResponseStatus status, JsonObject body) {
|
HttpResponseStatus status, JsonObject body) {
|
||||||
byte[] bytes = body.toString().getBytes(StandardCharsets.UTF_8);
|
byte[] bytes = body.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
|
|||||||
Reference in New Issue
Block a user