improbable-eng / grpc-web

gRPC Web implementation for Golang and TypeScript
Apache License 2.0
4.39k stars 435 forks source link

Support running grpc-web in React Native #141

Open begoat opened 6 years ago

begoat commented 6 years ago

I tried grpc-web today. But unfortunately errors occurred when send request. The error log is cannot resolve http module from node_module/grpc-web-client/dist/transports/nodeHttp.js. The version of grpc-web-client is 0.5.0 which is uploaded 3 days ago.

I try to install http npm package, but another error shows that the node_modules/http/package.json was successfully found but this package itself specifies a 'main' module field that couldn't be found.

I also try to install some browserified package instead for example stream-http and change the nodeHttp.js file's requirement, but seems that I will do many serious tweak.

Could someone give me some more suggestions? :)

easyCZ commented 6 years ago

Hi @begoat,

Thanks for raising. Will try to look into it today. Is there anything specific about your react-native setup or is it similar to the standard react-native setup?

begoat commented 6 years ago

I setup typescript workflow following this tutorial Thanks in advance. :)

easyCZ commented 6 years ago

Hi @begoat,

I can reproduce the problem but I do not have a fix yet. It appears that react-native does not allow the http or https modules from node to be imported. Because they are declared as top level imports rather than imported lazily, react-native errors as not being able to find them. I'll continue digging into.

begoat commented 6 years ago

:)

RobIsHere commented 6 years ago

Hi! I'm also looking into using this lib with react native.

React native is more browser-like than node-like. It should support fetch and XMLHttpRequest, see https://facebook.github.io/react-native/docs/network.html

Node transport is the last one tried by the factory. So it should select fetch which is first and as a fallback possibly XMLHttpRequest, but not node http.

The quesition is: is there a problem with the detection code itself, or is the detection code failing because of some missing sub-feature of fetch/XMLHttpRequest in RN?

Did you two find out more during debugging? Any hints, where the problem is?

rogchap commented 6 years ago

The issue is with RN. It's fetch API implementation does not support streaming: https://github.com/facebook/react-native/issues/9629

RobIsHere commented 6 years ago

Thank you for the hint. I guess, they don't support overrideMimeType, too.

So the remaining options are websockets like in https://github.com/improbable-eng/grpc-web/pull/137 with polling fallback. Or binding the native platform clients of grpc in RN.

easyCZ commented 6 years ago

The issue is two fold.

  1. RN appears to traverse the import tree and flags up any missing imports. Because the imports are traversed we reach a statement attempting to import http which is not available on RN. An error is displayed.

  2. The fetch transport does not fully support the features used by grpc-web as mentioned above. Websockets should help alleviate this problem.

rogchap commented 6 years ago

I would not recommend Websockets for Mobile; it drains your battery really fast. One benifits of gRPC is its efficiency I.e better on your battery. The best option for RN is to bind to the native gRPC implementations.

begoat commented 6 years ago

Yeah, currently I use both grpc-java and grpc-objectiveC in my react-native project instead. It won't take too much time to implement them and it works well.

linjson commented 6 years ago

@easyCZ, Did you mean grpc-web wouldn't used on react-native?

MikeSilvis commented 6 years ago

@easyCZ can we look at migrating away from this http client and into something that react-native will support?

RobIsHere commented 6 years ago

When I did look into this there has been absolutely no way to support grpc in RN. Every option I checked did not work because details have not been implemented in RN. Although they are writing that they support the necessary things in their docs, there are issues open that say: detail xyz is implemented different from the spec/not implemented. So you would probably have to send PRs zo RN first. I just want to throw this hint into the discussion so you can check up front and not when you‘ll have spent a lot of time on this 😉

MikeSilvis commented 6 years ago

Thanks Rob!

MikeSilvis commented 6 years ago

@RobIsHere did you try this? https://github.com/tradle/react-native-http

RobIsHere commented 6 years ago

No. But the readme says adapted from stream-http which say „It tries to match Node's API and behavior as closely as possible, but some features aren't available, since browsers don't give nearly as much control over requests.“ That’s the problem. RN stuff works like browsers. Grpc-web would be the only way to do it. But as I understood that‘s not yet available.

jonbretman commented 6 years ago

@begoat @easyCZ After many frustrating hours I got this library working in React Native yesterday using a mix of https://github.com/tradle/rn-nodeify and the following custom transport (based on the xhr one).

As already noted React Native has support for the Fetch API but does not support response.body.getReader() or response.arrayBuffer() so there is no way to get the response as raw bytes. I thought I'd be able to get it working with response.blob() and then FileReader.readAsDataURL but I couldn't seem to get the base64 string back into a Uint8Array without it being corrupted.

The transport below is based on the XHR transport with the following changes:

import { Metadata } from 'grpc-web-client/dist/metadata';
import {
    Transport,
    TransportOptions,
} from 'grpc-web-client/dist/transports/Transport';
import detach from 'grpc-web-client/dist/detach';

declare const XMLHttpRequest: any;

class XHR implements Transport {
    options: TransportOptions;
    xhr: any = null;
    metadata: Metadata | null = null;

    constructor(transportOptions: TransportOptions) {
        this.options = transportOptions;
    }

    onLoadEvent() {
        const result = new Uint8Array(this.xhr.response);
        detach(() => {
            this.options.onChunk(result);
        });
        detach(() => {
            this.options.onEnd();
        });
    }

    onStateChange() {
        if (this.xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
            detach(() => {
                this.options.onHeaders(
                    new Metadata(this.xhr.getAllResponseHeaders()),
                    this.xhr.status
                );
            });
        }
    }

    sendMessage(msgBytes: Uint8Array) {
        this.xhr.send(msgBytes);
    }

    finishSend() {}

    start(metadata: Metadata) {
        this.metadata = metadata;
        const xhr = new XMLHttpRequest();
        this.xhr = xhr;
        xhr.open('POST', this.options.url);
        (xhr as any).responseType = 'arraybuffer';
        this.metadata.forEach((key, values) => {
            xhr.setRequestHeader(key, values.join(', '));
        });
        xhr.addEventListener('readystatechange', this.onStateChange.bind(this));
        xhr.addEventListener('loadend', this.onLoadEvent.bind(this));
        xhr.addEventListener('error', (err: any) => {
            detach(() => {
                this.options.onEnd(err.error);
            });
        });
    }

    cancel() {
        this.xhr.abort();
    }
}

export default function xhrTransport(options: TransportOptions): Transport {
    return new XHR(options);
}

Hope this helps someone 😄

jonnyreeves commented 6 years ago

Thanks for the contribution!

Could you please consider raising this as pull request and we can look to get it integrated?

On Tue, 21 Aug 2018, 09:53 Jon Bretman, notifications@github.com wrote:

@begoat https://github.com/begoat @easyCZ https://github.com/easyCZ After many frustrating hours I got this library working in React Native yesterday using a mix of https://github.com/tradle/rn-nodeify and the following custom transport (based on the xhr one).

As already noted React Native has support for the Fetch API but does not support response.body.getReader() or response.arrayBuffer() so there is no way to get the response as raw bytes. I thought I'd be able to get it working with response.blob() and then FileReader.readAsDataURL but I couldn't seem to get the base64 string back into a Uint8Array without it being corrupted.

The transport below is based on the XHR transport with the following changes:

  • removed overrideMimeType as this is not supported in React Native
  • responseType is set to 'arraybuffer'
  • not listening for the 'progress' event - this never seems to fire
  • due to the previous point the response body is read in the 'loadend' handler

import { Metadata } from 'grpc-web-client/dist/metadata';import { Transport, TransportOptions, } from 'grpc-web-client/dist/transports/Transport';import detach from 'grpc-web-client/dist/detach'; declare const XMLHttpRequest: any; class XHR implements Transport { options: TransportOptions; xhr: any = null; metadata: Metadata | null = null;

constructor(transportOptions: TransportOptions) {
    this.options = transportOptions;
}

onLoadEvent() {
    const result = new Uint8Array(this.xhr.response);
    detach(() => {
        this.options.onChunk(result);
    });
    detach(() => {
        this.options.onEnd();
    });
}

onStateChange() {
    if (this.xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
        detach(() => {
            this.options.onHeaders(
                new Metadata(this.xhr.getAllResponseHeaders()),
                this.xhr.status
            );
        });
    }
}

sendMessage(msgBytes: Uint8Array) {
    this.xhr.send(msgBytes);
}

finishSend() {}

start(metadata: Metadata) {
    this.metadata = metadata;
    const xhr = new XMLHttpRequest();
    this.xhr = xhr;
    xhr.open('POST', this.options.url);
    (xhr as any).responseType = 'arraybuffer';
    this.metadata.forEach((key, values) => {
        xhr.setRequestHeader(key, values.join(', '));
    });
    xhr.addEventListener('readystatechange', this.onStateChange.bind(this));
    xhr.addEventListener('loadend', this.onLoadEvent.bind(this));
    xhr.addEventListener('error', (err: any) => {
        detach(() => {
            this.options.onEnd(err.error);
        });
    });
}

cancel() {
    this.xhr.abort();
}

} export default function xhrTransport(options: TransportOptions): Transport { return new XHR(options); }

Hope this helps someone 😄

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/improbable-eng/grpc-web/issues/141#issuecomment-414601236, or mute the thread https://github.com/notifications/unsubscribe-auth/AAMN-fo068v546K0rrFfTKcjmCV4RxTOks5uS8pzgaJpZM4SA2OM .

jonbretman commented 6 years ago

@jonnyreeves Yeh sure, probably won't have time for another week or so though. In the meantime I will continue to test out my solution on the app I'm working on. Haven't tried it on Android yet... 😟

johanbrandhorst commented 6 years ago

Lets recap; with the transport provided by @jonbretman, we could add support for React Native? Is that correct? Does anything else need to be done?

linjson commented 6 years ago

@johanbrandhorst, that's correct for support in React Native

jonbretman commented 6 years ago

I’m sorry I haven’t had time to look at this yet. It’s also worth noting that I haven’t tested this on Android so it might be just an iOS thing.

What I can say if we’re now successfully using this on iOS with no issues so pretty confident it works.

On Mon, 10 Sep 2018 at 01:48, linjson notifications@github.com wrote:

@johanbrandhorst https://github.com/johanbrandhorst, that's correct for support in React Native

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/improbable-eng/grpc-web/issues/141#issuecomment-419758134, or mute the thread https://github.com/notifications/unsubscribe-auth/ABl_cUwHpIeZUX0vMt_Dty5bar40_pEcks5uZbbGgaJpZM4SA2OM .

-- Jon Bretman

mobile: 0774 555 7381 email: jon.bretman@gmail.com

johanbrandhorst commented 6 years ago

@jonbretman that is very exciting to hear. Would you be interested in contributing this transport to the project?

jonbretman commented 6 years ago

I would but am just super busy right now. Might have some time over the next few weeks.

john-osullivan commented 6 years ago

Hey all, you seem to be some of the only ones running into my issue. Currently trying to use web3-providers-http in a React-Native app, but the build fails because the http module can't be imported. When I install it directly, the corresponding folder is added to node_modules, but the main index.js file is not installed, so it fails.

Are there really no work-arounds to let us use that module? Having to rewrite this provider to use a different request solution would be a real drag.

jonnyreeves commented 6 years ago

@john-osullivan #265 provides a solution by extracting the Node HTTP Transport out from the core project and into a separate module.

bourquep commented 5 years ago

FYI, I'm able to use @improbable-eng/grpc-web in React Native by using the @improbable-eng/grpc-web-node-http-transport transport.

In order for that transport to work in RN, I had to:

My main source for getting to this result was: https://github.com/parshap/node-libs-react-native

easyCZ commented 5 years ago

Awesome, thanks for posting this. It would be useful to create a react native example in the repo. Would you be interested in contributing? I'm happy to walk you through the contribution if needed.

bourquep commented 5 years ago

Unfortunately I don't have time for this now. Sorry.

pbsf commented 5 years ago

With #458 I was able to get gRPC-WEB working with RN. Looks pretty similar to what @jonbretman did.

mikebm commented 5 years ago

Any update to this? I haven't been able to get any of these solutions to work. The 'grpc-web-client' no longer has any of those files in list and bourquep's change results in errors. Would be nice to have built in support without hacking around the issue.

pojntfx commented 4 years ago

@mikebm You could take a look at https://github.com/improbable-eng/grpc-web/tree/master/client/grpc-web-react-native-transport

andrewkryshtal commented 4 years ago

hello guys! Thank you for such hard work around gRPC support in RN! I have issue with implementation, connection creates - but event onMessage doesn't triggers at all. Nothing shows in console at all in RN, but wireshark shows, that data flows correctly, but i can't check what kind of data because it's encoded with SSL.

I've tried with ReactNativeTransport and NodeHttpTransport, according to @bourquep instructions.

I have separate file with NodeHttpTransport implementation which works correctly in pure JS without any frameworks, so, i pretty sure, that our backend works well.

Any clues?

This is my app info:

"@improbable-eng/grpc-web": "^0.12.0",
"@improbable-eng/grpc-web-node-http-transport": "^0.12.0",
"@improbable-eng/grpc-web-react-native-transport": "^0.12.0",
"expo": "~36.0.0",
christianholman commented 4 years ago

hello guys! Thank you for such hard work around gRPC support in RN! I have issue with implementation, connection creates - but event onMessage doesn't triggers at all. Nothing shows in console at all in RN, but wireshark shows, that data flows correctly, but i can't check what kind of data because it's encoded with SSL.

I've tried with ReactNativeTransport and NodeHttpTransport, according to @bourquep instructions.

I have separate file with NodeHttpTransport implementation which works correctly in pure JS without any frameworks, so, i pretty sure, that our backend works well.

Any clues?

This is my app info:

"@improbable-eng/grpc-web": "^0.12.0",
"@improbable-eng/grpc-web-node-http-transport": "^0.12.0",
"@improbable-eng/grpc-web-react-native-transport": "^0.12.0",
"expo": "~36.0.0",

Did you ever figure this one out?

Yasaswini134 commented 3 years ago

Yeah, currently I use both grpc-java and grpc-objectiveC in my react-native project instead. It won't take too much time to implement them and it works well.

Hi, can you please suggest me a proper document on gRPC on react native

begoat commented 3 years ago

Yeah, currently I use both grpc-java and grpc-objectiveC in my react-native project instead. It won't take too much time to implement them and it works well.

Hi, can you please suggest me a proper document on gRPC on react native

https://reactnative.dev/docs/native-modules-ios https://reactnative.dev/docs/native-modules-android https://grpc.io/docs/languages/java/quickstart https://grpc.io/docs/languages/objective-c/basics

Hope this can help. And some comments above also mentioned some promising workaround that deserve a try.

Good luck.