diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index ae0eef0a..83f96ac3 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -19,7 +19,8 @@ package io.openvidu.server; import java.io.IOException; import java.net.MalformedURLException; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; import javax.annotation.PostConstruct; @@ -39,6 +40,7 @@ import org.springframework.context.event.EventListener; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.server.cdr.CDRLogger; import io.openvidu.server.cdr.CDRLoggerFile; import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.HttpHandshakeInterceptor; @@ -64,6 +66,7 @@ import io.openvidu.server.rpc.RpcNotificationService; import io.openvidu.server.utils.CommandExecutor; import io.openvidu.server.utils.GeoLocationByIp; import io.openvidu.server.utils.GeoLocationByIpDummy; +import io.openvidu.server.webhook.CDRLoggerWebhook; /** * OpenVidu Server application @@ -129,10 +132,15 @@ public class OpenViduServer implements JsonRpcConfigurer { @Bean @ConditionalOnMissingBean public CallDetailRecord cdr() { - if (this.openviduConfig.isCdrEnabled()) { + List loggers = new ArrayList<>(); + if (openviduConfig.isCdrEnabled()) { log.info("OpenVidu CDR is enabled"); + loggers.add(new CDRLoggerFile()); } - return new CallDetailRecord(Arrays.asList(new CDRLoggerFile())); + if (openviduConfig.isWebhookEnabled()) { + loggers.add(new CDRLoggerWebhook(openviduConfig)); + } + return new CallDetailRecord(loggers); } @Bean diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java index a84275b8..1361464d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREvent.java @@ -23,7 +23,7 @@ public class CDREvent { protected String sessionId; protected Long timeStamp; - private CDREventName eventName; + protected CDREventName eventName; public CDREvent(CDREventName eventName, String sessionId, Long timeStamp) { this.eventName = eventName; diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventParticipant.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventParticipant.java index 240025da..8afc0b32 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventParticipant.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventParticipant.java @@ -33,8 +33,8 @@ public class CDREventParticipant extends CDREventEnd { } // participantLeft - public CDREventParticipant(CDREventParticipant event, EndReason reason) { - super(CDREventName.participantLeft, event.getSessionId(), event.getTimestamp(), reason); + public CDREventParticipant(CDREventParticipant event, EndReason reason, Long timestamp) { + super(CDREventName.participantLeft, event.getSessionId(), event.getTimestamp(), reason, timestamp); this.participant = event.participant; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecording.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecording.java index 5fbaeb49..8f9a3681 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecording.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecording.java @@ -25,7 +25,7 @@ import io.openvidu.server.recording.Recording; public class CDREventRecording extends CDREventEnd { - private Recording recording; + protected Recording recording; // recordingStarted public CDREventRecording(String sessionId, Recording recording) { @@ -34,9 +34,9 @@ public class CDREventRecording extends CDREventEnd { } // recordingStopped - public CDREventRecording(CDREventRecording event, Recording recording, EndReason reason) { + public CDREventRecording(CDREventRecording event, Recording recording, EndReason reason, Long timestamp) { super(CDREventName.recordingStopped, event == null ? recording.getSessionId() : event.getSessionId(), - event == null ? recording.getCreatedAt() : event.getTimestamp(), reason); + event == null ? recording.getCreatedAt() : event.getTimestamp(), reason, timestamp); this.recording = recording; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecordingStatus.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecordingStatus.java new file mode 100644 index 00000000..e881c4f9 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventRecordingStatus.java @@ -0,0 +1,54 @@ +/* + * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) + * + * 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 io.openvidu.server.cdr; + +import com.google.gson.JsonObject; + +import io.openvidu.java.client.Recording.Status; +import io.openvidu.server.core.EndReason; +import io.openvidu.server.recording.Recording; + +public class CDREventRecordingStatus extends CDREventRecording { + + private Status status; + + public CDREventRecordingStatus(String sessionId, Recording recording, Status status) { + super(sessionId, recording); + this.eventName = CDREventName.recordingStatusChanged; + this.status = status; + } + + public CDREventRecordingStatus(CDREventRecording recordingStartedEvent, Recording recording, EndReason finalReason, + long timestamp, Status status) { + super(recordingStartedEvent, recording, finalReason, timestamp); + this.eventName = CDREventName.recordingStatusChanged; + this.status = status; + } + + public Status getStatus() { + return status; + } + + @Override + public JsonObject toJson() { + JsonObject json = super.toJson(); + json.addProperty("status", this.status.name()); + return json; + } + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventSession.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventSession.java index 5c8e3240..49c1b611 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventSession.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventSession.java @@ -31,8 +31,8 @@ public class CDREventSession extends CDREventEnd { } // sessionDestroyed - public CDREventSession(CDREventSession event, EndReason reason) { - super(CDREventName.sessionDestroyed, event.getSessionId(), event.getTimestamp(), reason); + public CDREventSession(CDREventSession event, EndReason reason, Long timestamp) { + super(CDREventName.sessionDestroyed, event.getSessionId(), event.getTimestamp(), reason, timestamp); this.session = event.session; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventWebrtcConnection.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventWebrtcConnection.java index aac35c4c..82fb6340 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventWebrtcConnection.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDREventWebrtcConnection.java @@ -41,8 +41,8 @@ public class CDREventWebrtcConnection extends CDREventEnd implements Comparable< } // webrtcConnectionDestroyed - public CDREventWebrtcConnection(CDREventWebrtcConnection event, EndReason reason) { - super(CDREventName.webrtcConnectionDestroyed, event.getSessionId(), event.getTimestamp(), reason); + public CDREventWebrtcConnection(CDREventWebrtcConnection event, EndReason reason, Long timestamp) { + super(CDREventName.webrtcConnectionDestroyed, event.getSessionId(), event.getTimestamp(), reason, timestamp); this.streamId = event.streamId; this.participant = event.participant; this.mediaOptions = event.mediaOptions; diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLogger.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLogger.java index 9c73dbf0..cd81171d 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLogger.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLogger.java @@ -28,6 +28,4 @@ public interface CDRLogger { public void log(SessionSummary sessionSummary); - public boolean canBeDisabled(); - } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLoggerFile.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLoggerFile.java index 5856f3ca..e3ff60a5 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLoggerFile.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CDRLoggerFile.java @@ -40,9 +40,4 @@ public class CDRLoggerFile implements CDRLogger { public void log(SessionSummary sessionSummary) { } - @Override - public boolean canBeDisabled() { - return true; - } - } diff --git a/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java b/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java index 4953e364..2d85cdf0 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java +++ b/openvidu-server/src/main/java/io/openvidu/server/cdr/CallDetailRecord.java @@ -26,6 +26,7 @@ import java.util.concurrent.ConcurrentSkipListSet; import org.springframework.beans.factory.annotation.Autowired; +import io.openvidu.java.client.Recording.Status; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.MediaOptions; @@ -49,6 +50,7 @@ import io.openvidu.server.summary.SessionSummary; * - 'webrtcConnectionDestroyed' {sessionId, timestamp, participantId, startTime, duration, connection, [receivingFrom], audioEnabled, videoEnabled, [videoSource], [videoFramerate], reason} * - 'recordingStarted' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size} * - 'recordingStopped' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size} + * - 'recordingStatusChanged' {sessionId, timestamp, id, name, hasAudio, hasVideo, resolution, recordingLayout, size, status} * * PROPERTIES VALUES: * @@ -71,6 +73,7 @@ import io.openvidu.server.summary.SessionSummary; * - resolution string * - recordingLayout: string * - size: number + * - status: string * - webrtcConnectionDestroyed.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "mediaServerDisconnect", "openviduServerStopped" * - participantLeft.reason: "unsubscribe", "unpublish", "disconnect", "networkDisconnect", "openviduServerStopped" * - sessionDestroyed.reason: "lastParticipantLeft", "openviduServerStopped" @@ -117,7 +120,8 @@ public class CallDetailRecord { public void recordSessionDestroyed(String sessionId, EndReason reason) { CDREventSession e = this.sessions.remove(sessionId); if (e != null) { - CDREventSession eventSessionEnd = new CDREventSession(e, RecordingManager.finalReason(reason)); + CDREventSession eventSessionEnd = new CDREventSession(e, RecordingManager.finalReason(reason), + System.currentTimeMillis()); this.log(eventSessionEnd); // Summary: log closed session @@ -134,7 +138,7 @@ public class CallDetailRecord { public void recordParticipantLeft(Participant participant, String sessionId, EndReason reason) { CDREventParticipant e = this.participants.remove(participant.getParticipantPublicId()); - CDREventParticipant eventParticipantEnd = new CDREventParticipant(e, reason); + CDREventParticipant eventParticipantEnd = new CDREventParticipant(e, reason, System.currentTimeMillis()); this.log(eventParticipantEnd); // Summary: update final user ended connection @@ -152,7 +156,7 @@ public class CallDetailRecord { public void stopPublisher(String participantPublicId, String streamId, EndReason reason) { CDREventWebrtcConnection eventPublisherEnd = this.publications.remove(participantPublicId); if (eventPublisherEnd != null) { - eventPublisherEnd = new CDREventWebrtcConnection(eventPublisherEnd, reason); + eventPublisherEnd = new CDREventWebrtcConnection(eventPublisherEnd, reason, System.currentTimeMillis()); this.log(eventPublisherEnd); // Summary: update final user ended publisher @@ -180,7 +184,8 @@ public class CallDetailRecord { eventSubscriberEnd = it.next(); if (senderPublicId.equals(eventSubscriberEnd.receivingFrom)) { it.remove(); - eventSubscriberEnd = new CDREventWebrtcConnection(eventSubscriberEnd, reason); + eventSubscriberEnd = new CDREventWebrtcConnection(eventSubscriberEnd, reason, + System.currentTimeMillis()); this.log(eventSubscriberEnd); // Summary: update final user ended subscriber @@ -196,23 +201,35 @@ public class CallDetailRecord { CDREventRecording recordingStartedEvent = new CDREventRecording(sessionId, recording); this.recordings.putIfAbsent(recording.getId(), recordingStartedEvent); this.log(new CDREventRecording(sessionId, recording)); + this.recordRecordingStatusChanged(sessionId, recording, Status.started); } public void recordRecordingStopped(String sessionId, Recording recording, EndReason reason) { CDREventRecording recordingStartedEvent = this.recordings.remove(recording.getId()); + final long timestamp = System.currentTimeMillis(); CDREventRecording recordingStoppedEvent = new CDREventRecording(recordingStartedEvent, recording, - RecordingManager.finalReason(reason)); + RecordingManager.finalReason(reason), timestamp); this.log(recordingStoppedEvent); + this.recordRecordingStatusChanged(recordingStartedEvent, recording, RecordingManager.finalReason(reason), + timestamp, Status.stopped); + // Summary: update ended recording sessionManager.getAccumulatedRecordings(sessionId).add(recordingStoppedEvent); } + public void recordRecordingStatusChanged(String sessionId, Recording recording, Status status) { + this.log(new CDREventRecordingStatus(sessionId, recording, status)); + } + + public void recordRecordingStatusChanged(CDREventRecording recordingStartedEvent, Recording recording, + EndReason finalReason, long timestamp, Status status) { + this.log(new CDREventRecordingStatus(recordingStartedEvent, recording, finalReason, timestamp, status)); + } + private void log(CDREvent event) { this.loggers.forEach(logger -> { - if (openviduConfig.isCdrEnabled() || !logger.canBeDisabled()) { - logger.log(event); - } + logger.log(event); }); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/ComposedRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/ComposedRecordingService.java index 3a092820..a2356c0f 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/ComposedRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/ComposedRecordingService.java @@ -37,9 +37,11 @@ import com.github.dockerjava.api.model.Volume; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.java.client.Recording.Status; import io.openvidu.java.client.RecordingLayout; import io.openvidu.java.client.RecordingProperties; import io.openvidu.server.OpenViduServer; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.Participant; @@ -63,8 +65,8 @@ public class ComposedRecordingService extends RecordingService { private DockerManager dockerManager; public ComposedRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader, - OpenviduConfig openviduConfig) { - super(recordingManager, recordingDownloader, openviduConfig); + OpenviduConfig openviduConfig, CallDetailRecord cdr) { + super(recordingManager, recordingDownloader, openviduConfig, cdr); this.dockerManager = new DockerManager(); } @@ -97,6 +99,7 @@ public class ComposedRecordingService extends RecordingService { return this.stopRecordingWithVideo(session, recording, reason); } else { recording = this.sealRecordingMetadataFileAsProcessing(recording); + this.cdr.recordRecordingStatusChanged(session.getSessionId(), recording, Status.processing); return this.stopRecordingAudioOnly(session, recording, reason, 0); } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java index 8f612bee..4af00fba 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingManager.java @@ -56,6 +56,7 @@ import io.openvidu.client.OpenViduException.Code; import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.Recording.OutputMode; import io.openvidu.java.client.RecordingProperties; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.Participant; @@ -91,6 +92,9 @@ public class RecordingManager { @Autowired private KmsManager kmsManager; + + @Autowired + private CallDetailRecord cdr; protected Map startingRecordings = new ConcurrentHashMap<>(); protected Map startedRecordings = new ConcurrentHashMap<>(); @@ -113,8 +117,8 @@ public class RecordingManager { RecordingManager.IMAGE_TAG = openviduConfig.getOpenViduRecordingVersion(); this.dockerManager = new DockerManager(); - this.composedRecordingService = new ComposedRecordingService(this, recordingDownloader, openviduConfig); - this.singleStreamRecordingService = new SingleStreamRecordingService(this, recordingDownloader, openviduConfig); + this.composedRecordingService = new ComposedRecordingService(this, recordingDownloader, openviduConfig, cdr); + this.singleStreamRecordingService = new SingleStreamRecordingService(this, recordingDownloader, openviduConfig, cdr); log.info("Recording module required: Downloading openvidu/openvidu-recording:" + openviduConfig.getOpenViduRecordingVersion() + " Docker image (350MB aprox)"); diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingService.java index e87bfe38..6871a895 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/RecordingService.java @@ -26,6 +26,7 @@ import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; import io.openvidu.java.client.RecordingLayout; import io.openvidu.java.client.RecordingProperties; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.Session; @@ -41,13 +42,15 @@ public abstract class RecordingService { protected OpenviduConfig openviduConfig; protected RecordingManager recordingManager; protected RecordingDownloader recordingDownloader; + protected CallDetailRecord cdr; protected CustomFileManager fileWriter = new CustomFileManager(); RecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader, - OpenviduConfig openviduConfig) { + OpenviduConfig openviduConfig, CallDetailRecord cdr) { this.recordingManager = recordingManager; this.recordingDownloader = recordingDownloader; this.openviduConfig = openviduConfig; + this.cdr = cdr; } public abstract Recording startRecording(Session session, RecordingProperties properties) throws OpenViduException; diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java index 24be866c..9c031f6f 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/service/SingleStreamRecordingService.java @@ -52,7 +52,9 @@ import com.google.gson.JsonObject; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.java.client.Recording.Status; import io.openvidu.java.client.RecordingProperties; +import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; import io.openvidu.server.core.Participant; @@ -71,8 +73,8 @@ public class SingleStreamRecordingService extends RecordingService { private final String INDIVIDUAL_STREAM_METADATA_FILE = ".stream."; public SingleStreamRecordingService(RecordingManager recordingManager, RecordingDownloader recordingDownloader, - OpenviduConfig openviduConfig) { - super(recordingManager, recordingDownloader, openviduConfig); + OpenviduConfig openviduConfig, CallDetailRecord cdr) { + super(recordingManager, recordingDownloader, openviduConfig, cdr); } @Override @@ -131,6 +133,7 @@ public class SingleStreamRecordingService extends RecordingService { @Override public Recording stopRecording(Session session, Recording recording, EndReason reason) { recording = this.sealRecordingMetadataFileAsProcessing(recording); + this.cdr.recordRecordingStatusChanged(session.getSessionId(), recording, Status.processing); return this.stopRecording(session, recording, reason, 0); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/webhook/CDRLoggerWebhook.java b/openvidu-server/src/main/java/io/openvidu/server/webhook/CDRLoggerWebhook.java new file mode 100644 index 00000000..abc82f87 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/webhook/CDRLoggerWebhook.java @@ -0,0 +1,59 @@ +/* + * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) + * + * 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 io.openvidu.server.webhook; + +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openvidu.server.cdr.CDREvent; +import io.openvidu.server.cdr.CDRLogger; +import io.openvidu.server.config.OpenviduConfig; +import io.openvidu.server.kurento.endpoint.KmsEvent; +import io.openvidu.server.summary.SessionSummary; + +public class CDRLoggerWebhook implements CDRLogger { + + private Logger log = LoggerFactory.getLogger(CDRLoggerWebhook.class); + + private HttpWebhookSender webhookSender; + + public CDRLoggerWebhook(OpenviduConfig openviduConfig) { + this.webhookSender = new HttpWebhookSender(openviduConfig.getOpenViduWebhookEndpoint(), + openviduConfig.getOpenViduWebhookHeaders(), openviduConfig.getOpenViduWebhookEvents()); + } + + @Override + public void log(CDREvent event) { + try { + this.webhookSender.sendHttpPostCallback(event); + } catch (IOException e) { + log.error("Error sending webhook event: {}", e.getMessage()); + } + } + + @Override + public void log(KmsEvent event) { + } + + @Override + public void log(SessionSummary sessionSummary) { + } + +} diff --git a/openvidu-server/src/main/java/io/openvidu/server/webhook/HttpWebhookSender.java b/openvidu-server/src/main/java/io/openvidu/server/webhook/HttpWebhookSender.java new file mode 100644 index 00000000..54c97265 --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/webhook/HttpWebhookSender.java @@ -0,0 +1,160 @@ +/* + * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) + * + * 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 io.openvidu.server.webhook; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; + +import org.apache.http.Header; +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicHeader; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.TrustStrategy; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import io.openvidu.server.cdr.CDREvent; +import io.openvidu.server.cdr.CDREventName; + +public class HttpWebhookSender { + + private static final Logger log = LoggerFactory.getLogger(HttpWebhookSender.class); + + private HttpClient httpClient; + private String httpEndpoint; + private List
headers; + private List events; + + public HttpWebhookSender(String httpEndpoint, List
headers, List events) { + this.httpEndpoint = httpEndpoint; + this.headers = headers; + + boolean contentTypeHeaderAdded = false; + for (Header header : this.headers) { + if (HttpHeaders.CONTENT_TYPE.equals(header.getName()) && "application/json".equals(header.getValue())) { + contentTypeHeaderAdded = true; + break; + } + } + + if (!contentTypeHeaderAdded) { + headers.add(new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json")); + } + + this.events = events; + + TrustStrategy trustStrategy = new TrustStrategy() { + @Override + public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { + return true; + } + }; + + SSLContext sslContext; + + try { + sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build(); + } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { + throw new RuntimeException(e); + } + + RequestConfig.Builder requestBuilder = RequestConfig.custom(); + requestBuilder = requestBuilder.setConnectTimeout(30000); + requestBuilder = requestBuilder.setConnectionRequestTimeout(30000); + + this.httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build()) + .setConnectionTimeToLive(30, TimeUnit.SECONDS).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) + .setSSLContext(sslContext).build(); + } + + /** + * @throws IOException If: A) The HTTP connection cannot be established to the + * endpoint B) The response received from the endpoint is + * not 200 + */ + public void sendHttpPostCallback(CDREvent event) throws IOException { + + if (!this.events.contains(event.getEventName())) { + return; + } + + HttpPost request = new HttpPost(httpEndpoint); + + StringEntity params = null; + try { + JsonObject jsonEvent = event.toJson(); + jsonEvent.addProperty("event", event.getEventName().name()); + params = new StringEntity(jsonEvent.toString()); + } catch (UnsupportedEncodingException e) { + log.error("Cannot create StringEntity from JSON CDREvent. Default HTTP charset is not supported"); + } + + for (Header header : this.headers) { + request.setHeader(header); + } + request.setEntity(params); + + HttpResponse response = null; + try { + response = this.httpClient.execute(request); + int statusCode = response.getStatusLine().getStatusCode(); + if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { + log.info("Event {} successfully posted to uri {}", event.getEventName().name(), this.httpEndpoint); + } else { + log.error("Unexpected HTTP status from callback endpoint {}: expected 200, received {}", httpEndpoint, + statusCode); + throw new IOException("Unexpected HTTP status " + statusCode); + } + } catch (ClientProtocolException e) { + String message = "ClientProtocolException posting event [" + event.getEventName().name() + "] to endpoint " + + httpEndpoint + ": " + e.getMessage(); + log.error(message); + throw new ClientProtocolException(message); + } catch (IOException e) { + String message = "IOException posting event [" + event.getEventName().name() + "] to endpoint " + + httpEndpoint + ": " + e.getMessage(); + log.error(message); + throw new IOException(message); + } finally { + if (response != null) { + EntityUtils.consumeQuietly(response.getEntity()); + } + } + } + +} diff --git a/openvidu-server/src/main/resources/application.properties b/openvidu-server/src/main/resources/application.properties index 037cb831..07a55ed4 100644 --- a/openvidu-server/src/main/resources/application.properties +++ b/openvidu-server/src/main/resources/application.properties @@ -17,10 +17,10 @@ openvidu.secret: MY_SECRET openvidu.cdr: false openvidu.cdr.path: log -openvidu.webhook: true -openvidu.webhook.endpoint: https://localhost/openvidu-endpoint -openvidu.webhook.headers: [\"Authorization:\ Basic\ T1BFTlZJRFVBUFA6TVlfU0VDUkVU\"] -openvidu.webhook.events: [\"sessionCreated\",\"sessionDestroyed\",\"recordingStatusChanged\"] +openvidu.webhook: false +openvidu.webhook.endpoint: +openvidu.webhook.headers: +openvidu.webhook.events: [\"sessionCreated\",\"sessionDestroyed\",\"participantJoined\",\"participantLeft\",\"webrtcConnectionCreated\",\"webrtcConnectionDestroyed\",\"recordingStatusChanged\"] openvidu.recording: false openvidu.recording.version: 2.9.0 diff --git a/openvidu-test-e2e/jenkins/Jenkinsfile b/openvidu-test-e2e/jenkins/Jenkinsfile index a76c6fba..bb6e74e5 100644 --- a/openvidu-test-e2e/jenkins/Jenkinsfile +++ b/openvidu-test-e2e/jenkins/Jenkinsfile @@ -57,10 +57,10 @@ node('container') { sh(script: '''#!/bin/bash if [ "$DOCKER_RECORDING_VERSION" != "default" ]; then echo "Using custom openvidu-recording tag: $DOCKER_RECORDING_VERSION" - cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.recording.version=$DOCKER_RECORDING_VERSION openvidu-server-*.jar &> /openvidu-server.log & + cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.recording.version=$DOCKER_RECORDING_VERSION -Dopenvidu.webhook=true -Dopenvidu.webhook.endpoint=http://172.17.0.1:7777/webhook/ openvidu-server-*.jar &> /openvidu-server.log & else echo "Using default openvidu-recording tag" - cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true openvidu-server-*.jar &> /openvidu-server.log & + cd openvidu/openvidu-server/target && java -jar -Dopenvidu.publicurl=https://172.17.0.1:4443/ -Dopenvidu.recording=true -Dopenvidu.webhook=true -Dopenvidu.webhook.endpoint=http://172.17.0.1:7777/webhook/ openvidu-server-*.jar &> /openvidu-server.log & fi '''.stripIndent()) sh 'until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do echo "Waiting for openvidu-server..."; sleep 2; done' diff --git a/openvidu-test-e2e/pom.xml b/openvidu-test-e2e/pom.xml index 09c1b35c..7bd3be63 100644 --- a/openvidu-test-e2e/pom.xml +++ b/openvidu-test-e2e/pom.xml @@ -164,6 +164,11 @@ ${version.openvidu.java.client} test + + org.springframework.boot + spring-boot-starter-web + ${version.spring-boot} + diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index 94f26b60..18febaa1 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -102,6 +102,7 @@ import io.openvidu.test.browsers.FirefoxUser; import io.openvidu.test.browsers.OperaUser; import io.openvidu.test.e2e.utils.CommandLineExecutor; import io.openvidu.test.e2e.utils.CustomHttpClient; +import io.openvidu.test.e2e.utils.CustomWebhook; import io.openvidu.test.e2e.utils.MultimediaFileMetadata; import io.openvidu.test.e2e.utils.Unzipper; @@ -2725,6 +2726,70 @@ public class OpenViduTestAppE2eTest { gracefullyLeaveParticipants(2); } + @Test + @DisplayName("Webhook test") + void webhookTest() throws Exception { + isRecordingTest = true; + + setupBrowser("chrome"); + + log.info("Webhook test"); + + CountDownLatch initLatch = new CountDownLatch(1); + CustomWebhook.main(new String[0], initLatch); + + try { + + if (!initLatch.await(30, TimeUnit.SECONDS)) { + Assert.fail("Tiemout waiting for webhook springboot app to start"); + CustomWebhook.shutDown(); + return; + } + + user.getDriver().findElement(By.id("add-user-btn")).click(); + user.getDriver().findElement(By.id("session-settings-btn-0")).click(); + Thread.sleep(1000); + user.getDriver().findElement(By.id("recording-mode-select")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("option-ALWAYS")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("output-mode-select")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("option-INDIVIDUAL")).click(); + Thread.sleep(500); + user.getDriver().findElement(By.id("save-btn")).click(); + Thread.sleep(1000); + + user.getDriver().findElement(By.className("join-btn")).click(); + + CustomWebhook.waitForEvent("sessionCreated", 2); + CustomWebhook.waitForEvent("participantJoined", 2); + CustomWebhook.waitForEvent("webrtcConnectionCreated", 2); + JsonObject event = CustomWebhook.waitForEvent("recordingStatusChanged", 10); + + Assert.assertEquals("Wrong recording status in webhook event", "started", event.get("status").getAsString()); + + user.getDriver().findElement(By.id("session-api-btn-0")).click(); + Thread.sleep(1000); + user.getDriver().findElement(By.id("close-session-btn")).click(); + user.getDriver().findElement(By.id("close-dialog-btn")).click(); + Thread.sleep(1000); + + CustomWebhook.waitForEvent("webrtcConnectionDestroyed", 2); + CustomWebhook.waitForEvent("participantLeft", 2); + event = CustomWebhook.waitForEvent("recordingStatusChanged", 2); + Assert.assertEquals("Wrong recording status in webhook event", "processing", event.get("status").getAsString()); + event = CustomWebhook.waitForEvent("recordingStatusChanged", 2); + Assert.assertEquals("Wrong recording status in webhook event", "stopped", event.get("status").getAsString()); + + CustomWebhook.waitForEvent("sessionDestroyed", 2); + + } finally { + CustomWebhook.shutDown(); + } + + } + private void listEmptyRecordings() { // List existing recordings (empty) user.getDriver().findElement(By.id("list-recording-btn")).click(); diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomWebhook.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomWebhook.java new file mode 100644 index 00000000..816c3312 --- /dev/null +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/utils/CustomWebhook.java @@ -0,0 +1,91 @@ +/* + * (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/) + * + * 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 io.openvidu.test.e2e.utils; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.TimeUnit; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.event.EventListener; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +@SpringBootApplication +public class CustomWebhook { + + private static ConfigurableApplicationContext context; + + public static CountDownLatch initLatch; + private static Map> events = new ConcurrentHashMap<>(); + private static JsonParser jsonParser = new JsonParser(); + + public static void main(String[] args, CountDownLatch initLatch) { + CustomWebhook.initLatch = initLatch; + SpringApplication app = new SpringApplication(CustomWebhook.class); + app.setDefaultProperties(Collections.singletonMap("server.port", "7777")); + CustomWebhook.context = app.run(args); + } + + public static void shutDown() { + CustomWebhook.context.close(); + } + + public static JsonObject waitForEvent(String eventName, int maxSecondsWait) throws Exception { + if (events.get(eventName) == null) { + events.put(eventName, new LinkedBlockingDeque<>()); + } + JsonObject event = CustomWebhook.events.get(eventName).poll(maxSecondsWait, TimeUnit.SECONDS); + if (event == null) { + throw new Exception("Timeout waiting for Webhook " + eventName); + } else { + return event; + } + } + + @RestController + public class GreetingController { + @RequestMapping("/webhook") + public void greeting(@RequestBody String eventString) { + JsonObject event = (JsonObject) jsonParser.parse(eventString); + final String eventName = event.get("event").getAsString(); + System.out.println(event.toString()); + if (events.get(eventName) == null) { + events.put(eventName, new LinkedBlockingDeque<>()); + } + CustomWebhook.events.get(eventName).add(event); + } + } + + @EventListener(ApplicationReadyEvent.class) + public void doSomethingAfterStartup() { + CustomWebhook.initLatch.countDown(); + } + +} diff --git a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html index b574e7bb..de5608d5 100644 --- a/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html +++ b/openvidu-testapp/src/app/components/dialogs/session-properties-dialog/session-properties-dialog.component.html @@ -11,16 +11,18 @@ - + - {{ enumerator }} + {{ enumerator }} - + - {{ enumerator }} + {{ enumerator }} @@ -31,8 +33,10 @@ - - + + @@ -73,26 +77,32 @@
- + - + - + - + - {{filterName}} + {{filterName}}
@@ -108,6 +118,7 @@ - +