How to proxy a HTTP video stream to any amount of clients through a Spring Webserver

Viewed 11568

Provided that I have a video HTTP stream broadcasted on a server that is on the same network as my Spring Webserver is, for instance in some url such as:

http://localhost:9090/httpstream

How can I proxy this video stream to any amount of clients, using Spring? The following example demonstrates the wanted flow:

  1. Spring webserver can be found at http://localhost:9091/spring
  2. A client wants to access a video stream, so he connects his video-stream-player to http://localhost:9091/spring (the spring webserver)
  3. The Spring WebServer should redirect the stream found on http://localhost:9090/httpstream to the client, with the latter never knowing that he accessed the httpstream host. The connection is made by Spring, not by the client

This is needed because the HTTPStream is an unsecured and not authenticated host, and I wanted to wrap it around a Spring Webserver, so that I could use some form of Security, such as a Basic Auth.


I tried requesting some form of mapping, but I couldn't find what kind of object to return, nor how to make the connection, but the expected behaviour should be something like this:

@Controller
public class HttpStreamProxyController {

    @RequestMapping("/spring") {
    public /*Stream Object?*/ getSecuredHttpStream() {
       if (clientIsSecured) {
       //... Security information

       return   //What should be returned?
       }
   }
}
5 Answers

I am not sure what kind of source you are using for generating your video stream (live camera or a video file or youtube video or ..)

You can probably use StreamingResponseBody (requires Spring 4.2+). Refer following links

http://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/streaming-response-body/

http://shazsterblog.blogspot.in/2016/02/asynchronous-streaming-request.html

Try this -

    @GetMapping("/stream1")
        @ResponseBody
        public StreamingResponseBody getVidoeStream1(@RequestParam String any) throws IOException {
            /* do security check before connecting to stream hosting server */ 
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<Resource> responseEntity = restTemplate.exchange( "http://localhost:8080/stream", HttpMethod.GET, null, Resource.class );
            InputStream st = responseEntity.getBody().getInputStream();
            return (os) -> {
                readAndWrite(st, os);
            };


    }

private void readAndWrite(final InputStream is, OutputStream os)
            throws IOException {
        byte[] data = new byte[2048];
        int read = 0;
        while ((read = is.read(data)) > 0) {
            os.write(data, 0, read);
        }
        os.flush();
    }

It should work. You can write your own implementation of readAndWrite() depending on your requirements.

So, your spring proxy controller could be something like this...

@Controller
public class HttpStreamProxyController {

    @RequestMapping("/spring") 
    @ResponseBody
    public StreamingResponseBody  getSecuredHttpStream() {
       if (clientIsSecured) {
       //... Security information

    RestTemplate restTemplate = new RestTemplate();
    // get video stream by connecting to stream hosting server  like this
            ResponseEntity<Resource> responseEntity = restTemplate.exchange( "https://ur-to-stream", HttpMethod.GET, null, Resource.class );
            InputStream st = responseEntity.getBody().getInputStream();
    // Or if there is any other preferred way of getting the video stream use that. The idea is to get the video input stream    

    // now return a StreamingResponseBody  object created by following lambda 
            return (os) -> {
                readAndWrite(st, os);
            };

       } else {
          return null;
       }

   }
}

The StreamingResponseBody returned by your rest endpoint will work fine with HTML5, which could be something like ..

<video width="320" height="240" controls>
  <source src="/spring" type="video/mp4">
  Your browser does not support the video tag
</video>

I struggled with this same problem for days, attempting to migrate my node app to Spring/React. I'm using a raspberry pi running Motion, which acts as a remote stream server that I was previously able to use the node module mjpeg-proxy to easily proxy the way OP wishes. This thread was the most useful set of examples in my search to do this in Java, however none of the examples quite worked for me. I'm hoping my controller class and component will help others attempting the same.

Please take note of response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString"); as the boundary=BoundaryString portion was the last critical piece. I opened the chrome debugger while connecting directly to the stream and this was in the response headers -- when I copied it, suddenly everything worked!


import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.net.URI;

@RestController
@RequestMapping(value = "/video")
public class VideoController {

    Logger log = LoggerFactory.getLogger(VideoController.class);

    @RequestMapping("/oculus")
    public void oculus(HttpServletResponse response) {
        log.info("Calling /video/oculus...");
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.execute(
                URI.create("http://oculus:8081"),
                HttpMethod.GET,
                (ClientHttpRequest request) -> {},
                responseExtractor -> {
                    response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString");
                    IOUtils.copy(responseExtractor.getBody(), response.getOutputStream());
                    return null;
                }
        );
    }

    @RequestMapping("/door")
    public void door(HttpServletResponse response) {
        log.info("Calling /video/door...");

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.execute(
                URI.create("http://vox:9002"),
                HttpMethod.GET,
                clientHttpRequest -> {
                    clientHttpRequest.getHeaders().add(HttpHeaders.AUTHORIZATION, "Basic blahblahBase64encodedUserAndPass=");
                },
                responseExtractor -> {
                    response.setContentType("multipart/x-mixed-replace; boundary=BoundaryString");
                    IOUtils.copy(responseExtractor.getBody(), response.getOutputStream());
                    return null;
                }
        );
    }

}

I have included examples for using Motion's basic auth, as well as an unprotected stream.

Both streams are then available on my app using the following bit of react js


class Video extends React.Component{
    render() {
        return (
            <div className="Content">
                <p>
                    <img className="stream" src="/video/oculus"/>
                </p>
                <p>
                    <img className="stream" src="/video/door"/>
                </p>
            </div>
        );
    }
}

export default Video;```

A good way to do video streaming in a Spring Boot application (I used a different source, however — a USB camera):

@GetMapping(value = "/stream")
public ResponseEntity<StreamingResponseBody> stream() {
    StreamingResponseBody stream = outputStream -> {
        while (streaming.get()) {
            final var raw = streamImage.get();
            final var jpeg = convert(byte2Buffered(raw.getImageData(), raw.getImageWidth(), raw.getImageHeight()));
            outputStream.write(jpeg);
        }
        outputStream.flush();
    };
    final var headers = new HttpHeaders();
    headers.add("Access-Control-Allow-Origin", "*");
    headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
    headers.add("Content-Type", "multipart/x-mixed-replace;boundary=frame");
    headers.add("Expires", "0");
    headers.add("Pragma", "no-cache");
    headers.add("Max-Age", "0");
    return ResponseEntity.ok()
            .headers(headers)
            .body(stream);
}

This way you'll get a stream on URL endpoint, which you can embed into a web page using img HTML tag.

you can give a try to kurento media server. http://doc-kurento.readthedocs.io/en/latest/tutorials/java/tutorial-one2many.html. It works with WEB-RTC and works well for my current project.

as they state:

You can use whatever Java server side technology you prefer to build web applications with Kurento. For example, a pure Java EE application, SIP Servlets, Play, Vertex, etc. We chose Spring Boot for convenience.

Solution with restTemplate exchange didn't work properly for video streams (mp4 in my case), cause request hangs on and client does not recieve any data. Take a look at execute method. Example:

import org.apache.poi.util.IOUtils;

@GetMapping("/video")    
public void process(HttpServletResponse response) {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.execute(            
        URI.create("https://some-url"),            
        HttpMethod.GET,            
        (ClientHttpRequest request) -> {},            
        responseExtractor -> {                
            response.setContentType("video/mp4");                
            IOUtils.copy(responseExtractor.getBody(), response.getOutputStream());                
            return null;            
        }
    );    
}

Related