Custom Scheme Handler doesn't read full response

Having problems with building or using the JCEF Java binding? Ask your questions here.

Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 8:30 am

I'm trying to create a custom scheme handler that provides an InputStream to chrome. The InputStream contains a wav file that I'm trying to use as the "src" of an audio element.

When it comes to load a resource with my custom handler, it works OK at first, but gets lost after the 6th call to readResponse().

Here is some logging to show how far it gets:

Code: Select all
In processRequest cn1stream://cn1app/streams/1 {Accept=*/*, User-Agent=Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/83.0.4103.88 Mobile/15E148 Safari/604.1, Accept-Encoding=identity;q=1, *;q=0, Accept-Language=en-US,en;q=0.9, Range=bytes=0-} handler=com.codename1.impl.javase.cef.InputStreamSchemeHandler@67b0d061
     [java]      [java] Stream found com.codename1.impl.javase.cef.StreamWrapper@34f03dc7 offset 0
     [java]      [java] In getResponseHeaders
     [java]      [java] InputStreamSchemeHandler:: null len=669736
     [java]      [java] Set response length to 669736
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse
     [java]      [java] readResponse:65536, 65536
     [java]      [java] Abbout to attempt reading 65536
     [java]      [java] Read 65536 from stream
     [java]      [java] Returning true
     [java]      [java] Exiting readResponse


Those are println statements in my custom scheme handler to show how far it's getting. To paraphrase:

1. It runs processRequest successfully.
2. It runs getResponseHeaders() successfully. It sets content length to 669736
3. It runs readResponse 6 times, each time loading the next 65536 bytes. (Total bytes is then 393216)
4. That's the last that *that* scheme handler instance hears from the browser.

After a short time, the browser tries to make additional requests to get the rest of the data.

These requests only get as far as processRequest:

Code: Select all
In processRequest cn1stream://cn1app/streams/1 {Accept=*/*, User-Agent=Mozilla/5.0 (iPhone; CPU iPhone OS 13_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/83.0.4103.88 Mobile/15E148 Safari/604.1, Accept-Encoding=identity;q=1, *;q=0, Accept-Language=en-US,en;q=0.9, Range=bytes=262144-} handler=com.codename1.impl.javase.cef.InputStreamSchemeHandler@1


It doesn't go on to call getResponseHeaders() for these requests.

One notable thing on these subsequent requests is that the Range query is asking for range starting at 262144 (=65536*4), but the first scheme handler fired 6 times, returning 327680 cumulative bytes. To the results of thelast two of the readResponse() calls were perhaps discarded. I don't know.

For reference, my scheme handler source code that produced that output is here:

Code: Select all
// Copyright (c) 2014 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

package com.codename1.impl.javase.cef;

import com.codename1.io.Log;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandlerAdapter;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;

/**
 * The example for the second scheme with domain handling is a more
 * complex example and is taken from the parent project CEF. Please
 * see CEF: "cefclient/scheme_test.cpp" for futher details
 */
public class InputStreamSchemeHandler extends CefResourceHandlerAdapter {
   

   
    public static final String scheme = "cn1stream";
    public static final String domain = "cn1app";
    //public static final String domain = "tests";
    private StreamWrapper stream;
   
    private byte[] data_;
    private String mime_type_;
    private int offset_ = 0;
    private boolean closed;
   
   

    public InputStreamSchemeHandler() {
        super();

    }
   
    public static String getURL(String streamId) {
        return scheme+"://"+domain+"/streams/"+streamId;
    }

    @Override
    public synchronized boolean processRequest(CefRequest request, CefCallback callback) {
       
        Map headerMap = new HashMap();
        request.getHeaderMap(headerMap);
        String range = (String)headerMap.get("Range");
       
        System.out.println("In processRequest "+request.getURL()+" " +headerMap+" handler="+this);
        String url = request.getURL();
        String streamId = null;
        if (url.indexOf("/") != -1) {
            streamId = url.substring(url.lastIndexOf("/")+1);
        }
       
        if (streamId != null) {
            stream = BrowserPanel.getStreamRegistry().getStream(streamId);
           
            if (range != null) {
                if (range.indexOf("=") != -1) {
                    range = range.substring(range.indexOf("=")+1);
                    String startStr = range.substring(0, range.indexOf("-"));
                    long start = Long.parseLong(startStr);
                    if (stream.getOffset() < start) {
                        InputStream inputStream = stream.getStream();
                        int seekTo = (int)(start- stream.getOffset());
                        try {
                            for (int i=0; i<seekTo; i++) {
                                inputStream.read();
                            }
                            stream.setOffset(start);
                        } catch (IOException ex) {}

                    }
                }

            }
        }
        System.out.println("Stream found "+stream+" offset "+stream.getOffset());
       
        callback.Continue();
       
       
        return stream != null;
    }

   
   
   
    @Override
    public void getResponseHeaders(
            CefResponse response, IntRef response_length, StringRef redirectUrl) {
        System.out.println("In getResponseHeaders");
        if (stream != null) {
            System.out.println("InputStreamSchemeHandler:: "+stream.getMimeType()+" len="+stream.getLength());
            String mime = stream.getMimeType();
            if (mime == null) {
                //mime = "application/octet-stream";
                mime = "audio/wav";
            }
           
            response.setMimeType(mime);
            response.setHeaderByName("Accept-Ranges", "bytes", true);
            response.setHeaderByName("Content-Length", ""+(stream.getLength() - stream.getOffset()), true);
            if (stream.getOffset() > 0) {
                response.setStatus(206);
            } else {
                response.setStatus(200);
            }
            response_length.set((int)(stream.getLength() - stream.getOffset()));
            System.out.println("Set response length to "+response_length.get());
           
        } else {
            String msg = "Not found";
            data_ = msg.getBytes();
            mime_type_ = "text/plain";
            response.setMimeType(mime_type_);
            response.setStatus(404);
            response_length.set(data_.length);
        }

    }

    @Override
    public synchronized boolean readResponse(
            byte[] data_out, int bytes_to_read, IntRef bytes_read, CefCallback callback) {
       
        System.out.println("readResponse:"+data_out.length+", "+bytes_to_read);
        try {
            boolean has_data = true;
            if (closed) {
                System.out.println("Stream was closed");
                bytes_read.set(0);
                return false;
            }
            if (stream == null) {
                has_data = false;

                if (offset_ < data_.length) {
                    // Copy the next block of data into the buffer.
                    int transfer_size = Math.min(bytes_to_read, (data_.length - offset_));
                    System.arraycopy(data_, offset_, data_out, 0, transfer_size);
                    offset_ += transfer_size;

                    bytes_read.set(transfer_size);
                    has_data = true;
                } else {
                    offset_ = 0;
                    bytes_read.set(0);
                }
                System.out.println("Stream was null");
                return has_data;
            }

            try {

                if (stream.getStream().available() > 0) {
                    System.out.println("Abbout to attempt reading "+bytes_to_read);
                    int read = stream.getStream().read(data_out, 0, bytes_to_read > 0 ? Math.min(bytes_to_read, data_out.length) : data_out.length);
                    System.out.println("Read "+read+" from stream");
                    if (read == -1) {
                        System.out.println("Reached the end of the stream");
                        has_data = false;
                        bytes_read.set(0);
                        stream.getStream().close();
                        closed = true;
                        BrowserPanel.getStreamRegistry().removeStream(stream);
                        stream = null;
                    } else {
                        //System.out.println("Zero bytes were available");
                        long oldOffset = stream.getOffset();
                        oldOffset += read;
                        stream.setOffset(oldOffset);
                        bytes_read.set(read);
                    }

                } else {
                    System.out.println("No bytes available");
                    bytes_read.set(0);
                }
            } catch (IOException ex) {
                Log.e(ex);
            }
            System.out.println("Returning "+has_data);
            callback.Continue();
            return has_data;
        } finally {
            System.out.println("Exiting readResponse");
        }
    }
   

   
}



Any suggestions on possible areas of exploration?
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 11:04 am

Digging through the code a little bit, I see that "ReadResponse" is deprecated:

From cef_resource_handler.h

Code: Select all
 ///
  // Read response data. If data is available immediately copy up to
  // |bytes_to_read| bytes into |data_out|, set |bytes_read| to the number of
  // bytes copied, and return true. To read the data at a later time set
  // |bytes_read| to 0, return true and call CefCallback::Continue() when the
  // data is available. To indicate response completion return false.
  //
  // WARNING: This method is deprecated. Use Skip and Read instead.
  ///
  /*--cef()--*/
  virtual bool ReadResponse(void* data_out,
                            int bytes_to_read,
                            int& bytes_read,
                            CefRefPtr<CefCallback> callback) {
    bytes_read = -2;
    return false;
  }


I wonder if CEF is using Read and Skip instead of ReadResponse, and thus bypassing the JNI callback. I'm not well versed in the C/C++ codebase yet, so this is a bit of a shot in the dark.
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby magreenblatt » Wed Jul 08, 2020 12:47 pm

You can find the JCEF code here. JCEF does not implement the Read method so ReadResponse is called. The code is JCEF is pretty simple, so I suspect the issue will be with your Java code or possibly in the CEF/Chromium implementation.
magreenblatt
Site Admin
 
Posts: 12408
Joined: Fri May 29, 2009 6:57 pm

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 1:42 pm

Thanks for the reply. That is the code I've been looking at. I've tried quite a few variations. It just isn't calling readResponse() as many times as it should. It never reaches the end of the request. I don't think the issue is with my Java code. The sample "ClientSchemeHandler.java" that is part of the Java CEF test app has the same problems when you try to return an audio file from it instead of a tiny HTML snippet.

My working hypothesis is that the custom schemes don't handle media requests or range requests completely. I've spent 2 days on this strategy and have no further leads, so must conclude that it is a problem with CEF/range requests.

I'm going to try to change my strategy to saving the stream as a file, then using a file:/// url - though that is a can of worms I didn't want to open. My last resort will be encoding as a data URL and injecting it using javascript, but that will likely hit walls for large-ish files too.
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby magreenblatt » Wed Jul 08, 2020 1:57 pm

What CEF version are you using? Some range-related issues were fixed in v81 (see this issue).
magreenblatt
Site Admin
 
Posts: 12408
Joined: Fri May 29, 2009 6:57 pm

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 2:00 pm

JCEF Version = 81.2.24.251
CEF Version = 81.2.24
Chromium Version = 81.0.4044.113
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 2:02 pm

That sounds like exactly the bug though. I'm getting the same partial range headers. And I get the same FFmpegDemuxer errors.
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 2:15 pm

Ah. I see there were some commits on June 30th that update to a new CEF version. I'm still working off a version that I cloned a few weeks ago (last update May 21). I'll merge those changes into my fork and try rebuilding.
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby shannah » Wed Jul 08, 2020 4:23 pm

I updated to the latest in git

Code: Select all
     [java]      [java] JCEF Version = 83.4.0.259
     [java]      [java] CEF Version = 83.4.0
     [java]      [java] Chromium Version = 83.0.4103.106


I'm experiencing the same issue still, and there are new problems. Rendering is noticeably sluggish (I'm using a custom renderer that was inspired by the JOGL OSR renderer, but renders to a BufferedImage instead --- but it is much slower than the previous version).

I also see this error logged on start, which is probably related to the rendering issue. ERROR:viz_process_transport_factory.cc(308)] Switching to software compositing.
shannah
Techie
 
Posts: 30
Joined: Wed Jul 08, 2020 8:01 am

Re: Custom Scheme Handler doesn't read full response

Postby magreenblatt » Wed Jul 08, 2020 5:20 pm

Did you update all binaries for the new CEF version, including the swiftshader directory?
magreenblatt
Site Admin
 
Posts: 12408
Joined: Fri May 29, 2009 6:57 pm

Next

Return to JCEF Forum

Who is online

Users browsing this forum: No registered users and 15 guests