8000 Dio Web: Migration from XMLHttpRequest to new fetch api by Binozo · Pull Request #2376 · cfug/dio · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Dio Web: Migration from XMLHttpRequest to new fetch api #2376

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from

Conversation

Binozo
Copy link
@Binozo Binozo commented Feb 6, 2025

Hello 👋

Since I wanted to make use of SSE but since #1740 was in the way I migrated the dio web adapter from XMLHttpRequest to the new fetch api

New Pull Request Checklist

  • I have read the Documentation
  • I have searched for a similar pull request in the project and found none
  • I have updated this branch with the latest main branch to avoid conflicts (via merge from master or rebase)
  • I have added the required tests to prove the fix/feature I'm adding
  • I have updated the documentation (if necessary)
  • I have run the tests without failures
  • I have updated the CHANGELOG.md in the correspondin 8000 g package

@Binozo Binozo requested a review from a team as a code owner February 6, 2025 20:34
@Zekfad
Copy link
Zekfad commented Feb 7, 2025

Part of this code was likely copied from here: https://github.com/Zekfad/fetch_api/blob/dee32249d9ecb4bf6b4eb05062930b5d704423c9/lib/src/js_promise_or.dart
I believe there must be a copyright notice as per license: https://github.com/Zekfad/fetch_api/blob/master/LICENSE#L4C48-L5C65
Besides for this PR I think bindings from package:web is now should be sufficient, fetch_api bindings were made when js_interop were just becoming a thing.

@Binozo
Copy link
Author
Binozo commented Feb 7, 2025

Indeed, what would you suggest? Either rewriting js_promise_or.dart or adding the license. Where should I add the license?

Additionally, the bindings from package:web are not sufficient. ReadableStream is not fully implemented in the standard package:web package. I wanted to fully support Dio's api, so I needed those auto generated apis for stream upload. To be more precise I needed those for this part:

      final streamReader = ReadableStream(
        ReadableStreamSource.fromStream(
          requestStream.map((e) {

           [...]

            sentBytes += e.lengthInBytes;
            if (options.onSendProgress != null) {
              options.onSendProgress!(sentBytes, -1);
            }
            return e.toJS;
          }),
        ),
      );

The dart doc https://pub.dev/documentation/web/latest/web/ReadableStream-extension-type.html shows the following method signature: ReadableStream([JSObject underlyingSource, QueuingStrategy strategy]) .

The mdn webdocs show this example: https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#examples:

    return new ReadableStream({
      start(controller) {
           [...]
      },
    });

Sadly I was not able to implement this this way. That's way I chose those auto generated files.

@Zekfad
Copy link
Zekfad commented Feb 7, 2025

Just add short notice in header in borrowed files, that's sufficient enough.

I thought by now package:web would have more coverage, since it's generated from webdl as opposed to fetch_api which was made by hand.

Also good job for bringing fetch implementation for Dio, hope they'll accept your PR!

@Binozo
Copy link
Author
Binozo commented Feb 7, 2025

Yes, also hope for an accept of my pr because I actually need this feature myself 😄

@Binozo
Copy link
Author
Binozo commented Feb 8, 2025

I think I need some help.

I can't get my head around with the tests, they are so confusing. As an example:

cors_tests.dart:92 asks for an Dio connectionError exception:

      test('POST with onSendProgress is preflighted', () async {
        // If there is a preflight (OPTIONS) request, the server fails it
        // by responding with status 418. This fails CORS, so the browser
        // never sends the main request and this code throws.
        expect(
          () async {
            final _ = await dio.post(
              '/status/418',
              data: 'body text',
              options: Options(
                validateStatus: (status) => true,
                contentType: Headers.textPlainContentType,
              ),
              onSendProgress: (_, __) {},
            );
          },
          throwsDioException(
            DioExceptionType.connectionError,
            stackTraceContains: 'test/test_suite_test.dart',
          ),
        );
      });

Which gets thrown in the code:

    // Check CORS
    if (response.status == 418) {
      if (options.onSendProgress != null || sendTimeout > Duration.zero || (request.method != 'GET' && options.contentType != Headers.textPlainContentType)) {
        completer.completeError(
          DioException.connectionError(
            requestOptions: options,
            error: 'CORS preflight request failed',
            reason: 'The preflight request responded with status 418',
          ),
          StackTrace.current,
        );
      }
    }

Which the test logs confirm:

[dio]: 00:45 +93 -4: test/test_suite_test.dart: CORS preflight POST with onSendProgress is preflighted [E]                                                                                                    
[dio]:   DioException [connection error]: The connection errored: The preflight request responded with status 418 This indicates an error which most likely cannot be solved by the library.
[dio]:   Error: CORS preflight request failed
[dio]:   org-dartlang-sdk:///lib/_internal/js_runtime/lib/core_patch.dart 788:58            StackTrace.current
[dio]:   package:dio_web_adapter/src/adapter.dart 238:22                                    BrowserHttpClientAdapter.<fn>
[dio]:   org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 311:19           _wrapJsFunctionForAsync.<fn>
[dio]:   org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 336:23           call
[dio]:   package:stack_trace                                                                _run
[dio]:   org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 287:19           call
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1422:46                                    _rootRunUnary
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1323:34                                    runUnary
[dio]:   org-dartlang-sdk:///lib/async/future_impl.dart 169:29                              call
[dio]:   org-dartlang-sdk:///lib/async/future_impl.dart 931:13                              _Future._propagateToListeners
[dio]:   org-dartlang-sdk:///lib/async/future_impl.dart 707:5                               _completeWithValue
[dio]:   org-dartlang-sdk:///lib/async/future_impl.dart 777:7                               call
[dio]:   package:stack_trace                                                                call
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1414:12                                    _rootRun
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1316:34                                    run
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1225:7                                     runGuarded
[dio]:   org-dartlang-sdk:///lib/async/zone.dart 1265:18                                    call
[dio]:   org-dartlang-sdk:///lib/async/schedule_microtask.dart 40:12                        _microtaskLoop
[dio]:   org-dartlang-sdk:///lib/async/schedule_microtask.dart 49:5                         _startMicrotaskLoop
[dio]:   org-dartlang-sdk:///lib/_internal/js_runtime/lib/async_patch.dart 56:10            call
[dio]:   ===== asynchronous gap ===========================

But the test still fails 😕

@AlexV525
Copy link
Member

Checking the workflow seems some other exceptions were thrown:

[dio]: ::group::❌ test/test_suite_test.dart: send progress (failed)
[dio]: Error: Failed to execute 'close' on 'ReadableStreamDefaultController': Cannot close an errored readable stream
[dio]: package:dio_web_adapter/src/fetch/readable_stream_source.dart 67:13

@Binozo
Copy link
Author
Binozo commented Mar 22, 2025

Indeed thanks for the hint. Although I still can't figure out how to make the CORS Tests succeed

@AlexV525
Copy link
Member

Could you also update the branch so the workflow can run?

Binozo and others added 2 commits March 26, 2025 22:04
Signed-off-by: Binozo <70137898+Binozo@users.noreply.github.com>
@Binozo
Copy link
Author
Binozo commented Mar 26, 2025

sure

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you update the adapter_impl.dart?

Copy link
Author
@Binozo Binozo Mar 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes somehow I messed up the merge really bad 🙂

Is it now right?

@AlexV525
Copy link
Member

@Zekfad, what is your idea about these borrowed files? It seems the PR borrows a lot. Would creating a new adapter to bridge the fetch_api and dio be sufficient?

@Zekfad
Copy link
Zekfad commented Mar 27, 2025

I'm keeping fetch_api updated for recent versions of Dart, so if you're ok with adding a dependency then it's good by me. That was the reason I made it as a separate package from fetch_client from the beginning.

On a side note, I'm currently implementing a feature check for request streaming (it can't check if server is HTTP/3, but it can at least ensure that browser have the capability) and there is that discrepancy of closing dart stream after AbortController did it, need some time to make a proper fix.

If you're strongly against adding a new dependency, since its ISC licensed I'd ok with just copying relevant files as long as attribution is retained.

@AlexV525
Copy link
Member

I'm keeping fetch_api updated for recent versions of Dart, so if you're ok with adding a dependency then it's good by me. That was the reason I made it as a separate package from fetch_client from the beginning.

@Zekfad: Thanks for keeping the library updated! What I was trying to say is we should probably make a plugin like dio_fetch_adapter to combine fetch_api and dio, or we can make a small guide about using the dio_compatibility_layer to integrate with fetch_client. That way, we might keep all our implementations well-constructed and easily maintained.

@Binozo
Copy link
Author
Binozo commented Mar 27, 2025

I had the same idea about adding fetch_api as a new dependency but I changed my mind as I wanted to keep this PR as dependency less as possible. But you have to decide @AlexV525

@AlexV525
Copy link
Member

I had the same idea about adding fetch_api as a new dependency but I changed my mind as I wanted to keep this PR as dependency less as possible. But you have to decide @AlexV525

@Binozo: Would you be able to create a separate PR that bridges the fetch_client+dio_compatibility_layer or fetch_api+dio? I'd like to evaluate which is better.

@Binozo Binozo closed this Mar 29, 2025
@adar2378
Copy link

Hi, was there any decision made yet? @Binozo Any disussion or something is open to follow the solutions? I am using Dio for my projects. I am really looking forward to use fetch_client with Dio for streaming SSE.

@Binozo
Copy link
Author
Binozo commented May 22, 2025

Hi @adar2378 I didn't have time to make a decision yet, I proceeded to use this way instead:

import 'package:http/http.dart' as http;

final dio = Dio();
final client = http.Client();
final dioAdapter = ConversionLayerAdapter(client);
dio.httpClientAdapter = dioAdapter;

@adar2378
Copy link

@Binozo I would be very grateful if you could point me to the ConversationLayerAdapter ? I imagine it would require to write an adapter following this:
https://github.com/cfug/dio/tree/main/plugins/web_adapter/lib/src but using fetch_client package?

@adar2378
Copy link

@Binozo
I see you meant this package to be used along with http package, as http package supports streaming response for web?
https://github.com/cfug/dio/tree/main/plugins/compatibility_layer

@Binozo
Copy link
Author
Binozo commented Jun 14, 2025

@adar2378 no this package is not meant to be used along with the http package as it would make no sense. Both Dio and http are http clients. The snippet I provided is just a workaround until the Dio package migrates to fetch instead of using XMLHttpRequest. The http package already uses the newer fetch client so we make use of it by using it as an adapter for dio.

Also if you want to create a ConversionLayerAdapter you can look at this pr code as it already does it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants
0