/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.io.rest.sse; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.security.RolesAllowed; import javax.inject.Singleton; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; import org.eclipse.smarthome.core.auth.Role; import org.eclipse.smarthome.core.events.Event; import org.eclipse.smarthome.io.rest.sse.internal.SseEventOutput; import org.eclipse.smarthome.io.rest.sse.internal.util.SseUtil; import org.glassfish.jersey.media.sse.EventOutput; import org.glassfish.jersey.media.sse.SseBroadcaster; import org.glassfish.jersey.media.sse.SseFeature; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; /** * SSE Resource for pushing events to currently listening clients. * * @author Ivan Iliev - Initial Contribution and API * @author Yordan Zhelev - Added Swagger annotations * */ @Path(SseResource.PATH_EVENTS) @RolesAllowed({ Role.USER }) @Singleton @Api(value = SseResource.PATH_EVENTS, hidden = true) public class SseResource { public final static String PATH_EVENTS = "events"; private static final String X_ACCEL_BUFFERING_HEADER = "X-Accel-Buffering"; private final SseBroadcaster broadcaster; private final ExecutorService executorService; @Context private UriInfo uriInfo; @Context private HttpServletResponse response; @Context private HttpServletRequest request; public SseResource() { this.executorService = Executors.newSingleThreadExecutor(); this.broadcaster = new SseBroadcaster(); } /** * Subscribes the connecting client to the stream of events filtered by the * given eventFilter. * * @param eventFilter * @return {@link EventOutput} object associated with the incoming * connection. * @throws IOException * @throws InterruptedException */ @GET @Produces(SseFeature.SERVER_SENT_EVENTS) @ApiOperation(value = "Get all events.", response = EventOutput.class) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 400, message = "Topic is empty or contains invalid characters") }) public Object getEvents(@QueryParam("topics") @ApiParam(value = "topics") String eventFilter) throws IOException, InterruptedException { if (!SseUtil.isValidTopicFilter(eventFilter)) { return Response.status(Status.BAD_REQUEST).build(); } // construct an EventOutput that will only write out events that match // the given filter final EventOutput eventOutput = new SseEventOutput(eventFilter); broadcaster.add(eventOutput); // Disables proxy buffering when using an nginx http server proxy for this response. // This allows you to not disable proxy buffering in nginx and still have working sse response.addHeader(X_ACCEL_BUFFERING_HEADER, "no"); if (!SseUtil.SERVLET3_SUPPORT) { // if we don't have sevlet 3.0 async support, we want to make sure // that the response is not compressed and buffered so that the // client receives server sent events at the moment of sending them response.addHeader(HttpHeaders.CONTENT_ENCODING, "identity"); // Response headers are written now, since the thread will be // blocked later on. response.setStatus(HttpServletResponse.SC_OK); response.setContentType(SseFeature.SERVER_SENT_EVENTS); response.flushBuffer(); // enable blocking for this thread SseUtil.enableBlockingSse(); } return eventOutput; } /** * Broadcasts an event described by the given parameter to all currently * listening clients. * * @param sseEventType * the SSE event type * @param event * the event */ public void broadcastEvent(final Event event) { executorService.execute(new Runnable() { @Override public void run() { broadcaster.broadcast(SseUtil.buildEvent(event)); } }); } }