1
1
package com .transloadit .sdk ;
2
2
3
+ import com .launchdarkly .eventsource .ConnectStrategy ;
4
+ import com .launchdarkly .eventsource .EventSource ;
5
+ import com .launchdarkly .eventsource .MessageEvent ;
6
+ import com .launchdarkly .eventsource .background .BackgroundEventHandler ;
7
+ import com .launchdarkly .eventsource .background .BackgroundEventSource ;
3
8
import com .transloadit .sdk .exceptions .LocalOperationException ;
4
9
import com .transloadit .sdk .exceptions .RequestException ;
5
10
import com .transloadit .sdk .response .AssemblyResponse ;
6
- import io .socket .client .IO ;
7
- import io .socket .client .Socket ;
8
- import io .socket .emitter .Emitter ;
9
- import io .socket .engineio .client .transports .WebSocket ;
10
11
import io .tus .java .client .ProtocolException ;
11
12
import io .tus .java .client .TusClient ;
12
13
import io .tus .java .client .TusURLMemoryStore ;
13
14
import io .tus .java .client .TusURLStore ;
14
15
import io .tus .java .client .TusUpload ;
15
16
import org .jetbrains .annotations .TestOnly ;
17
+ import org .json .JSONArray ;
16
18
import org .json .JSONObject ;
17
19
18
20
import java .io .File ;
19
21
import java .io .FileNotFoundException ;
20
22
import java .io .IOException ;
21
23
import java .io .InputStream ;
22
- import java .net .MalformedURLException ;
23
- import java .net .URISyntaxException ;
24
+ import java .net .URI ;
24
25
import java .net .URL ;
25
26
import java .util .ArrayList ;
26
27
import java .util .HashMap ;
29
30
import java .util .concurrent .Executors ;
30
31
import java .util .concurrent .ThreadPoolExecutor ;
31
32
import java .util .UUID ;
33
+ import java .util .concurrent .TimeUnit ;
32
34
// CHECKSTYLE:OFF
33
35
import io .tus .java .client .TusUploader ;
34
36
// CHECKTYLE:ON
@@ -48,7 +50,7 @@ public class Assembly extends OptionsBuilder {
48
50
protected boolean shouldWaitForCompletion ;
49
51
protected AssemblyListener assemblyListener ;
50
52
protected AssemblyListener runnableAssemblyListener ;
51
- protected Socket socket ;
53
+ protected BackgroundEventSource backgroundEventSource ;
52
54
53
55
54
56
protected ArrayList <TusUploadRunnable > threadList ;
@@ -252,8 +254,8 @@ public AssemblyResponse save(boolean isResumable)
252
254
throw new RequestException ("Request to Assembly failed: " + response .json ().getString ("error" ));
253
255
}
254
256
255
- if (shouldWaitWithSocket ()) {
256
- listenToSocket (response );
257
+ if (shouldWaitWithSSE ()) {
258
+ listenToServerSentEvents (response );
257
259
}
258
260
259
261
try {
@@ -265,12 +267,12 @@ public AssemblyResponse save(boolean isResumable)
265
267
}
266
268
} else {
267
269
response = new AssemblyResponse (request .post (obtainUploadUrlSuffix (), options , null , files , fileStreams ));
268
- if (shouldWaitWithSocket () && !response .isFinished ()) {
269
- listenToSocket (response );
270
+ if (shouldWaitWithSSE () && !response .isFinished ()) {
271
+ listenToServerSentEvents (response );
270
272
}
271
273
}
272
274
273
- return shouldWaitWithoutSocket () ? waitTillComplete (response ) : response ;
275
+ return shouldWaitWithoutSSE () ? waitTillComplete (response ) : response ;
274
276
}
275
277
276
278
/**
@@ -498,115 +500,117 @@ public void onAssemblyResultFinished(String stepName, JSONObject result) {
498
500
* <li>{@code false} if the client should not wait for completion by observing the HTTP - Response</li></ul>
499
501
* @see Assembly#save(boolean) Usage in Assembly.save()
500
502
*/
501
- protected boolean shouldWaitWithoutSocket () {
503
+ protected boolean shouldWaitWithoutSSE () {
502
504
return this .shouldWaitForCompletion && this .assemblyListener == null ;
503
505
}
504
506
505
507
/**
506
- * Determines if the Client should wait until the Assembly execution is finished by observing a server socket . <p>
508
+ * Determines if the Client should wait until the Assembly execution is finished by observing a server sent events (SSE) . <p>
507
509
* Can only be {@code true} if <code> {@link #shouldWaitForCompletion} = true</code> and an
508
510
* {@link AssemblyListener} has been specified.</p>
509
- * @return <ul><li>{@code true} if the client should wait for Assembly completion by observing the socket </li>
510
- * <li>{@code false} if the client should not wait for completion by observing the socket .</li></ul>
511
+ * @return <ul><li>{@code true} if the client should wait for Assembly completion by observing SSE </li>
512
+ * <li>{@code false} if the client should not wait for completion by observing the SSE .</li></ul>
511
513
* @see Assembly#save(boolean) Usage in Assembly.save()
512
514
*/
513
- protected boolean shouldWaitWithSocket () {
515
+ protected boolean shouldWaitWithSSE () {
514
516
return this .shouldWaitForCompletion && this .assemblyListener != null ;
515
517
}
516
518
517
- /**
518
- * Opens a Websocket to the provided URL in order to receive updates on the assembly's execution status.
519
- * @param socketUrl target url to open the WebSocket at.
520
- * @return {@link Socket}
521
- * @throws LocalOperationException
522
- */
523
- Socket getSocket (String socketUrl ) throws LocalOperationException {
524
- IO .Options options = new IO .Options ();
525
- options .transports = new String [] {WebSocket .NAME };
526
- try {
527
- URL url = new URL (socketUrl );
528
- options .path = url .getPath ();
529
- String host = url .getProtocol () + "://" + url .getHost ();
530
- return IO .socket (host , options );
531
- } catch (URISyntaxException | MalformedURLException e ) {
532
- throw new LocalOperationException (e );
533
- }
534
- }
535
-
536
519
/**
537
520
* Wait till the assembly is finished and then return the response of the complete state.
538
521
*
539
522
* @param response {@link AssemblyResponse}
540
- * @throws LocalOperationException if something goes wrong while running non-http operations.
541
523
*/
542
- private void listenToSocket (AssemblyResponse response ) throws LocalOperationException {
543
- final String assemblyUrl = response .getSslUrl ( );
544
- final String assemblyId = response . getId ();
524
+ private void listenToServerSentEvents (AssemblyResponse response ) {
525
+ final URI sseUpdateStreamUrl = URI . create ( response .getUpdateStreamUrl () );
526
+
545
527
546
- socket = getSocket (response .getWebsocketUrl ());
547
- Emitter .Listener onFinished = new Emitter .Listener () {
528
+ BackgroundEventHandler myHandler = new BackgroundEventHandler () {
548
529
@ Override
549
- public void call (Object ... args ) {
550
- socket .disconnect ();
551
- try {
552
- getAssemblyListener ().onAssemblyFinished (transloadit .getAssemblyByUrl (assemblyUrl ));
553
- } catch (RequestException e ) {
554
- getAssemblyListener ().onError (e );
555
- } catch (LocalOperationException e ) {
556
- getAssemblyListener ().onError (e );
557
- }
530
+ public void onOpen () {
558
531
}
559
- };
560
532
561
- Emitter .Listener onConnect = new Emitter .Listener () {
562
533
@ Override
563
- public void call (Object ... args ) {
564
- JSONObject obj = new JSONObject ();
565
- obj .put ("id" , assemblyId );
566
- socket .emit ("assembly_connect" , obj );
534
+ public void onClosed () {
535
+ }
536
+ // Here the different SSE sent events are getting piped to the corresponding assembly event via the {@link AssemblyListener}
537
+ public void onMessage (String event , MessageEvent messageEvent ) {
538
+ if (event != null && messageEvent != null ) {
539
+ // In case of a message event, without additional payload.
540
+ if (event .equals ("message" )) {
541
+ String messageContent = messageEvent .getData ();
542
+ if (messageContent .equals ("assembly_finished" )) {
543
+ try {
544
+ getAssemblyListener ().onAssemblyFinished (transloadit .getAssemblyByUrl (response .getSslUrl ()));
545
+ } catch (RequestException | LocalOperationException e ) {
546
+ getAssemblyListener ().onError (e );
547
+ } finally {
548
+ // Close the event source, as the assembly encoding process has finished.
549
+ getBackgroundEventSource ().close ();
550
+ }
551
+ }
552
+
553
+ if (messageContent .equals ("assembly_uploading_finished" )) {
554
+ getAssemblyListener ().onAssemblyUploadFinished ();
555
+ }
556
+
557
+ if (messageContent .equals ("assembly_upload_meta_data_extracted" )) {
558
+ getAssemblyListener ().onMetadataExtracted ();
559
+ }
560
+
561
+ // In case of regular events, which are coming with extra payloads from the server.
562
+ } else {
563
+ // Some events are not wrapped inside a plain JSON Object, but inside a JSON array.
564
+ JSONArray messageEventArray = new JSONArray (messageEvent .getData ());
565
+
566
+ if (event .equals ("assembly_result_finished" )) {
567
+ // Unpack the two expected fields in the JSON array.
568
+ String stepName = messageEventArray .getString (0 );
569
+ JSONObject messageEventJson = messageEventArray .getJSONObject (1 );
570
+ getAssemblyListener ().onAssemblyResultFinished (stepName , messageEventJson );
571
+ }
572
+
573
+ if (event .equals ("assembly_error" )) {
574
+ // Deliver error information to the user.
575
+ JSONObject messageEventJson = messageEventArray .getJSONObject (0 );
576
+ String errorString = messageEventJson .getString ("error" ) + "\n " + messageEventJson .toString (2 );
577
+ getAssemblyListener ().onError (new RequestException (errorString ));
578
+ }
579
+
580
+ if (event .equals ("assembly_upload_finished" )) {
581
+ JSONObject messageEventJson = messageEventArray .getJSONObject (0 );
582
+ getAssemblyListener ().onFileUploadFinished (messageEventJson .getString ("name" ), messageEventJson );
583
+ }
584
+ }
585
+ }
567
586
}
568
- };
569
587
570
- Emitter .Listener onError = new Emitter .Listener () {
571
588
@ Override
572
- public void call (Object ... args ) {
573
- socket .disconnect ();
574
- getAssemblyListener ().onError ((Exception ) args [0 ]);
589
+ public void onComment (String comment ) {
575
590
}
576
- };
577
591
578
- Emitter . Listener onMetadataExtracted = args -> {
579
- getAssemblyListener (). onMetadataExtracted ();
580
- };
592
+ @ Override
593
+ public void onError ( Throwable t ) {
594
+ }
581
595
582
- Emitter .Listener onAssemblyResultFinished = args -> {
583
- String stepName = (String ) args [0 ];
584
- JSONObject result = (JSONObject ) args [1 ];
585
- getAssemblyListener ().onAssemblyResultFinished (stepName , result );
586
596
};
597
+ this .backgroundEventSource = new BackgroundEventSource .Builder (myHandler , new EventSource .Builder (
598
+ ConnectStrategy .http (sseUpdateStreamUrl )
599
+ .header ("Accept" , "text/event-stream" )
600
+ .connectTimeout (5 , TimeUnit .SECONDS )
601
+ )
602
+ ).build ();
587
603
588
- //Hands over Filename of recently uploaded file to the callback in the AssemblyListener
589
- Emitter .Listener onFileUploadFinished = args -> {
590
- String name = ((JSONObject ) args [0 ]).getString ("name" );
591
- JSONObject uploadInformation = (JSONObject ) args [0 ];
592
- getAssemblyListener ().onFileUploadFinished (name , uploadInformation );
593
- };
604
+ this .backgroundEventSource .start ();
594
605
595
- // Triggers callback in the {@link Assembly#assemblyListener} if the Assembly instructions have been uploaded.
596
- Emitter .Listener onAssemblyUploadFinished = args -> {
597
- getAssemblyListener ().onAssemblyUploadFinished ();
598
- };
606
+ }
599
607
600
- socket
601
- .on (Socket .EVENT_CONNECT , onConnect )
602
- .on ("assembly_finished" , onFinished )
603
- .on ("assembly_uploading_finished" , onAssemblyUploadFinished )
604
- .on ("assembly_upload_finished" , onFileUploadFinished )
605
- .on ("assembly_upload_meta_data_extracted" , onMetadataExtracted )
606
- .on ("assembly_result_finished" , onAssemblyResultFinished )
607
- .on ("assembly_error" , onFinished )
608
- .on (Socket .EVENT_CONNECT_ERROR , onError );
609
- socket .connect ();
608
+ /**
609
+ * Returns the Event Source, which handles the Server Sent Event driven assembly status updates.
610
+ * @return BackgroundEventSource, which handles the assembly Status via SSE.
611
+ */
612
+ public BackgroundEventSource getBackgroundEventSource () {
613
+ return backgroundEventSource ;
610
614
}
611
615
612
616
/**
@@ -733,8 +737,8 @@ protected void abortUploads(Exception e) {
733
737
executor .shutdownNow ();
734
738
}
735
739
runnableAssemblyListener .onError (e );
736
- if (socket != null ) {
737
- socket . disconnect ();
740
+ if (backgroundEventSource != null ) {
741
+ backgroundEventSource . close ();
738
742
}
739
743
}
740
744
0 commit comments