All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cometd.server.http.AbstractStreamHttpTransport Maven / Gradle / Ivy

There is a newer version: 8.0.6
Show newest version
/*
 * Copyright (c) 2008-2022 the original author or authors.
 *
 * 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.cometd.server.http;

import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.cometd.bayeux.Channel;
import org.cometd.bayeux.Promise;
import org.cometd.bayeux.server.ServerMessage;
import org.cometd.server.BayeuxServerImpl;
import org.cometd.server.ServerSessionImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

The base class for HTTP transports that use blocking stream I/O.

*/ public abstract class AbstractStreamHttpTransport extends AbstractHttpTransport { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractStreamHttpTransport.class); private static final String CONTEXT_ATTRIBUTE = "org.cometd.transport.context"; private static final String HEARTBEAT_TIMEOUT_ATTRIBUTE = "org.cometd.transport.heartbeat.timeout"; protected AbstractStreamHttpTransport(BayeuxServerImpl bayeux, String name) { super(bayeux, name); } @Override public void handle(HttpServletRequest request, HttpServletResponse response) { // API calls could be async, so we must be async in the request processing too. AsyncContext asyncContext = request.startAsync(); // Explicitly disable the timeout, to prevent // that the timeout fires in case of slow reads. asyncContext.setTimeout(0); Promise promise = new Promise<>() { @Override public void succeed(Void result) { asyncContext.complete(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling successful"); } } @Override public void fail(Throwable failure) { int code = failure instanceof TimeoutException ? getDuplicateMetaConnectHttpResponseCode() : HttpServletResponse.SC_INTERNAL_SERVER_ERROR; sendError(request, response, code, failure); asyncContext.complete(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling failed", failure); } } }; Context context = (Context)request.getAttribute(CONTEXT_ATTRIBUTE); if (context == null) { process(new Context(request, response), promise); } else { ServerMessage.Mutable message = context.scheduler.getMessage(); context.session.notifyResumed(message, (Boolean)request.getAttribute(HEARTBEAT_TIMEOUT_ATTRIBUTE)); resume(context, message, Promise.from(y -> flush(context, promise), promise::fail)); } } protected void process(Context context, Promise promise) { HttpServletRequest request = context.request; try { try { ServerMessage.Mutable[] messages = parseMessages(request); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed {} messages", messages == null ? -1 : messages.length); } if (messages != null) { processMessages(context, List.of(messages), promise); } else { promise.succeed(null); } } catch (ParseException x) { handleJSONParseException(request, context.response, x.getMessage(), x.getCause()); promise.succeed(null); } } catch (Throwable x) { promise.fail(x); } } @Override protected HttpScheduler suspend(Context context, Promise promise, ServerMessage.Mutable message, long timeout) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Suspended {}", message); } HttpServletRequest request = context.request; context.scheduler = newHttpScheduler(context, promise, message, timeout); request.setAttribute(CONTEXT_ATTRIBUTE, context); context.session.notifySuspended(message, timeout); return context.scheduler; } protected HttpScheduler newHttpScheduler(Context context, Promise promise, ServerMessage.Mutable message, long timeout) { return new DispatchingLongPollScheduler(context, promise, message, timeout); } protected abstract ServerMessage.Mutable[] parseMessages(HttpServletRequest request) throws IOException, ParseException; protected ServerMessage.Mutable[] parseMessages(String[] requestParameters) throws IOException, ParseException { if (requestParameters == null || requestParameters.length == 0) { throw new IOException("Missing '" + MESSAGE_PARAM + "' request parameter"); } if (requestParameters.length == 1) { return parseMessages(requestParameters[0]); } List messages = new ArrayList<>(); for (String batch : requestParameters) { if (batch == null) { continue; } ServerMessage.Mutable[] parsed = parseMessages(batch); if (parsed != null) { messages.addAll(List.of(parsed)); } } return messages.toArray(new ServerMessage.Mutable[0]); } @Override protected void write(Context context, List messages, Promise promise) { HttpServletRequest request = context.request; HttpServletResponse response = context.response; try { ServerSessionImpl session = context.session; List replies = context.replies; int replyIndex = 0; boolean needsComma = false; ServletOutputStream output; try { output = beginWrite(request, response); // First message is always the handshake reply, if any. if (replies.size() > 0) { ServerMessage.Mutable reply = replies.get(0); if (Channel.META_HANDSHAKE.equals(reply.getChannel())) { if (allowMessageDeliveryDuringHandshake(session) && !messages.isEmpty()) { reply.put("x-messages", messages.size()); } getBayeux().freeze(reply); writeMessage(context, output, reply); needsComma = true; ++replyIndex; } } // Write the messages. for (ServerMessage message : messages) { if (needsComma) { output.write(','); } needsComma = true; writeMessage(context, output, message); } } finally { // Start the interval timeout after writing the messages // since they may take time to be written, even in case // of exceptions to make sure the session can be swept. if (context.scheduleExpiration) { scheduleExpiration(session, context.metaConnectCycle); } } // Write the replies, if any. while (replyIndex < replies.size()) { ServerMessage.Mutable reply = replies.get(replyIndex); if (needsComma) { output.write(','); } needsComma = true; getBayeux().freeze(reply); writeMessage(context, output, reply); ++replyIndex; } endWrite(response, output); promise.succeed(null); writeComplete(context, messages); } catch (Throwable x) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Failure writing messages", x); } promise.fail(x); } } protected void writeMessage(Context context, ServletOutputStream output, ServerMessage message) throws IOException { writeMessage(context.response, output, context.session, message); } protected void writeMessage(HttpServletResponse response, ServletOutputStream output, ServerSessionImpl session, ServerMessage message) throws IOException { output.write(toJSONBytes(message)); } protected abstract ServletOutputStream beginWrite(HttpServletRequest request, HttpServletResponse response) throws IOException; protected abstract void endWrite(HttpServletResponse response, ServletOutputStream output) throws IOException; protected void writeComplete(Context context, List messages) { } protected class DispatchingLongPollScheduler extends LongPollScheduler { public DispatchingLongPollScheduler(Context context, Promise promise, ServerMessage.Mutable message, long timeout) { super(context, promise, message, timeout); } @Override protected void dispatch(boolean timeout) { // We dispatch() when either we are suspended or timed out, instead of doing a write() + complete(). // If we have to write a message to 10 clients, and the first client write() blocks, then we would // be delaying the other 9 clients. // By always calling dispatch() we allow each write to be on its own thread, and it may block without // affecting other writes. // Only with Servlet 3.1 and standard asynchronous I/O we would be able to do write() + complete() // without blocking, and it will be much more efficient because there is no thread dispatching and // there will be more mechanical sympathy. HttpServletRequest request = getContext().request; request.setAttribute(HEARTBEAT_TIMEOUT_ATTRIBUTE, timeout); AsyncContext asyncContext = getAsyncContext(request); if (asyncContext != null) { asyncContext.dispatch(); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy