diff --git a/.github/labeler.yml b/.github/labeler.yml
index 7ac52ce..6d477b3 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,16 +1,21 @@
-# Configuration for .github/workflows/pull_request_label.yml. 
+# Configuration for .github/workflows/pull_request_label.yml.
 
-'infra':
-  - '.github/**'
+'type-infra':
+  - changed-files:
+      - any-glob-to-any-file: '.github/**'
 
 'package:cronet_http':
-  - 'pkgs/cronet_http/**'
+  - changed-files:
+      - any-glob-to-any-file: 'pkgs/cronet_http/**'
 
 'package:cupertino_http':
-  - 'pkgs/cupertino_http/**'
+  - changed-files:
+      - any-glob-to-any-file: 'pkgs/cupertino_http/**'
 
 'package:http':
-  - 'pkgs/http/**'
+  - changed-files:
+      - any-glob-to-any-file: 'pkgs/http/**'
 
 'package:http_client_conformance_tests':
-  - 'pkgs/http_client_conformance_tests/**'
+  - changed-files:
+      - any-glob-to-any-file: 'pkgs/http_client_conformance_tests/**'
\ No newline at end of file
diff --git a/.github/workflows/cronet.yml b/.github/workflows/cronet.yml
index 94fcf1c..1e9a81d 100644
--- a/.github/workflows/cronet.yml
+++ b/.github/workflows/cronet.yml
@@ -6,10 +6,12 @@
       - main
       - master
     paths:
+      - '.github/workflows/cronet.yaml'
       - 'pkgs/cronet_http/**'
       - 'pkgs/http_client_conformance_tests/**'
   pull_request:
     paths:
+      - '.github/workflows/cronet.yaml'
       - 'pkgs/cronet_http/**'
       - 'pkgs/http_client_conformance_tests/**'
   schedule:
@@ -19,48 +21,49 @@
   PUB_ENVIRONMENT: bot.github
 
 jobs:
-  analyze:
-    name: Lint and static analysis
-    runs-on: ubuntu-latest
-    defaults:
-      run:
-        working-directory: pkgs/cronet_http
-    steps:
-      - uses: actions/checkout@v4
-      - uses: subosito/flutter-action@v2
-        with:
-          # TODO: Change to 'stable' when a release version of flutter
-          # pins version 1.21.1 or later of 'package:test'
-          channel: 'master'
-      - id: install
-        name: Install dependencies
-        run: flutter pub get
-      - name: Check formatting
-        run: dart format --output=none --set-exit-if-changed .
-        if: always() && steps.install.outcome == 'success'
-      - name: Analyze code
-        run: flutter analyze --fatal-infos
-        if: always() && steps.install.outcome == 'success'
-
-  test:
-    # Test package:cupertino_http use flutter integration tests.
-    needs: analyze
-    name: "Build and test"
+  verify:
+    name: Format & Analyze & Test
     runs-on: macos-latest
+    strategy:
+      matrix:
+        package: ['cronet_http', 'cronet_http_embedded']
     steps:
-      - uses: actions/checkout@v4
-      - uses: actions/setup-java@v3
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93
         with:
           distribution: 'zulu'
           java-version: '17'
-      - uses: subosito/flutter-action@v2
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: 'stable'
+      - name: Make cronet_http_embedded copy
+        if: ${{ matrix.package == 'cronet_http_embedded' }}
+        run: |
+          mv pkgs/cronet_http pkgs/cronet_http_embedded
+          cd pkgs/cronet_http_embedded
+          flutter pub get && dart tool/prepare_for_embedded.dart
+      - id: install
+        name: Install dependencies
+        working-directory: 'pkgs/${{ matrix.package }}'
+        run: flutter pub get
+      - name: Check formatting
+        working-directory: 'pkgs/${{ matrix.package }}'
+        run: dart format --output=none --set-exit-if-changed .
+        if: always() && steps.install.outcome == 'success'
+      - name: Analyze code
+        working-directory: 'pkgs/${{ matrix.package }}'
+        run: flutter analyze --fatal-infos
+        if: always() && steps.install.outcome == 'success'
       - name: Run tests
-        uses: reactivecircus/android-emulator-runner@v2
+        uses: reactivecircus/android-emulator-runner@6b0df4b0efb23bb0ec63d881db79aefbc976e4b2
+        if: always() && steps.install.outcome == 'success'
         with:
-          api-level: 28
-          target: playstore
+          # api-level/minSdkVersion should be help in sync in:
+          # - .github/workflows/cronet.yml
+          # - pkgs/cronet_http/android/build.gradle
+          # - pkgs/cronet_http/example/android/app/build.gradle
+          api-level: 21
           arch: x86_64
+          target: ${{ matrix.package == 'cronet_http_embedded' && 'default' || 'google_apis' }}
           profile: pixel
-          script: cd ./pkgs/cronet_http/example && flutter test --timeout=1200s integration_test/
+          script: cd 'pkgs/${{ matrix.package }}/example' && flutter test --timeout=1200s integration_test/
diff --git a/.github/workflows/cupertino.yml b/.github/workflows/cupertino.yml
index 6617635..ef2a8e8 100644
--- a/.github/workflows/cupertino.yml
+++ b/.github/workflows/cupertino.yml
@@ -28,8 +28,8 @@
       run:
         working-directory: pkgs/cupertino_http
     steps:
-      - uses: actions/checkout@v4
-      - uses: subosito/flutter-action@v2
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: 'stable'
       - id: install
@@ -57,17 +57,17 @@
       matrix:
         # Test on the minimum supported flutter version and the latest
         # version.
-        flutter-version: ["3.10.0", "any"]
+        flutter-version: ["3.16.0", "any"]
     runs-on: macos-latest
     defaults:
       run:
         working-directory: pkgs/cupertino_http/example
     steps:
-      - uses: actions/checkout@v4
-      - uses: futureware-tech/simulator-action@v3
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: futureware-tech/simulator-action@bfa03d93ec9de6dacb0c5553bbf8da8afc6c2ee9
         with:
           model: 'iPhone 8'
-      - uses: subosito/flutter-action@v2
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           flutter-version: ${{ matrix.flutter-version }}
           channel: 'stable'
diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml
index 35af167..3de19a3 100644
--- a/.github/workflows/dart.yml
+++ b/.github/workflows/dart.yml
@@ -1,4 +1,4 @@
-# Created with package:mono_repo v6.6.0
+# Created with package:mono_repo v6.6.1
 name: Dart CI
 on:
   push:
@@ -21,7 +21,7 @@
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable"
@@ -29,76 +29,37 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - name: mono_repo self validate
-        run: dart pub global activate mono_repo 6.6.0
+        run: dart pub global activate mono_repo 6.6.1
       - name: mono_repo self validate
         run: dart pub global run mono_repo generate --validate
   job_002:
-    name: "analyze_and_format; linux; Dart 2.19.0; PKG: pkgs/http_client_conformance_tests; `dart analyze --fatal-infos`"
+    name: "analyze_and_format; linux; Dart 3.0.0; PKG: pkgs/http_profile; `dart analyze --fatal-infos`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:2.19.0;packages:pkgs/http_client_conformance_tests;commands:analyze_1"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile;commands:analyze_1"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.19.0;packages:pkgs/http_client_conformance_tests
-            os:ubuntu-latest;pub-cache-hosted;sdk:2.19.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
-        with:
-          sdk: "2.19.0"
-      - id: checkout
-        name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_client_conformance_tests_pub_upgrade
-        name: pkgs/http_client_conformance_tests; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http_client_conformance_tests
-      - name: "pkgs/http_client_conformance_tests; dart analyze --fatal-infos"
-        run: dart analyze --fatal-infos
-        if: "always() && steps.pkgs_http_client_conformance_tests_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http_client_conformance_tests
-  job_003:
-    name: "analyze_and_format; linux; Dart 3.0.0; PKGS: pkgs/http, pkgs/http_profile; `dart analyze --fatal-infos`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http-pkgs/http_profile;commands:analyze_1"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http-pkgs/http_profile
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile
             os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: "3.0.0"
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_pub_upgrade
-        name: pkgs/http; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http
-      - name: "pkgs/http; dart analyze --fatal-infos"
-        run: dart analyze --fatal-infos
-        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_profile_pub_upgrade
         name: pkgs/http_profile; dart pub upgrade
         run: dart pub upgrade
@@ -108,12 +69,72 @@
         run: dart analyze --fatal-infos
         if: "always() && steps.pkgs_http_profile_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/http_profile
+  job_003:
+    name: "analyze_and_format; linux; Dart 3.2.0; PKG: pkgs/http_client_conformance_tests; `dart analyze --fatal-infos`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http_client_conformance_tests;commands:analyze_1"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0;packages:pkgs/http_client_conformance_tests
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.2.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: "3.2.0"
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_client_conformance_tests_pub_upgrade
+        name: pkgs/http_client_conformance_tests; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http_client_conformance_tests
+      - name: "pkgs/http_client_conformance_tests; dart analyze --fatal-infos"
+        run: dart analyze --fatal-infos
+        if: "always() && steps.pkgs_http_client_conformance_tests_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http_client_conformance_tests
   job_004:
+    name: "analyze_and_format; linux; Dart 3.3.0; PKG: pkgs/http; `dart analyze --fatal-infos`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http;commands:analyze_1"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: "3.3.0"
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_pub_upgrade
+        name: pkgs/http; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http
+      - name: "pkgs/http; dart analyze --fatal-infos"
+        run: dart analyze --fatal-infos
+        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http
+  job_005:
     name: "analyze_and_format; linux; Dart dev; PKGS: pkgs/http, pkgs/http_client_conformance_tests, pkgs/http_profile; `dart analyze --fatal-infos`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http-pkgs/http_client_conformance_tests-pkgs/http_profile;commands:analyze_1"
@@ -123,12 +144,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: dev
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_pub_upgrade
         name: pkgs/http; dart pub upgrade
         run: dart pub upgrade
@@ -156,12 +177,12 @@
         run: dart analyze --fatal-infos
         if: "always() && steps.pkgs_http_profile_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/http_profile
-  job_005:
+  job_006:
     name: "analyze_and_format; linux; Dart dev; PKGS: pkgs/http, pkgs/http_client_conformance_tests, pkgs/http_profile; `dart format --output=none --set-exit-if-changed .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http-pkgs/http_client_conformance_tests-pkgs/http_profile;commands:format"
@@ -171,12 +192,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: dev
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_pub_upgrade
         name: pkgs/http; dart pub upgrade
         run: dart pub upgrade
@@ -204,12 +225,12 @@
         run: "dart format --output=none --set-exit-if-changed ."
         if: "always() && steps.pkgs_http_profile_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/http_profile
-  job_006:
+  job_007:
     name: "analyze_and_format; linux; Flutter stable; PKG: pkgs/flutter_http_example; `dart format --output=none --set-exit-if-changed .`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:pkgs/flutter_http_example;commands:format"
@@ -219,12 +240,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -234,12 +255,12 @@
         run: "dart format --output=none --set-exit-if-changed ."
         if: "always() && steps.pkgs_flutter_http_example_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/flutter_http_example
-  job_007:
+  job_008:
     name: "analyze_and_format; linux; Flutter stable; PKG: pkgs/flutter_http_example; `flutter analyze --fatal-infos`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:pkgs/flutter_http_example;commands:analyze_0"
@@ -249,12 +270,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -264,112 +285,27 @@
         run: flutter analyze --fatal-infos
         if: "always() && steps.pkgs_flutter_http_example_pub_upgrade.conclusion == 'success'"
         working-directory: pkgs/flutter_http_example
-  job_008:
-    name: "unit_test; linux; Dart 3.0.0; PKG: pkgs/http; `dart run --define=no_default_http_client=true test/no_default_http_client_test.dart`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http;commands:command_1"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
-        with:
-          sdk: "3.0.0"
-      - id: checkout
-        name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_pub_upgrade
-        name: pkgs/http; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http
-      - name: "pkgs/http; dart run --define=no_default_http_client=true test/no_default_http_client_test.dart"
-        run: "dart run --define=no_default_http_client=true test/no_default_http_client_test.dart"
-        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
   job_009:
-    name: "unit_test; linux; Dart 3.0.0; PKG: pkgs/http; `dart test --platform chrome`"
+    name: "unit_test; linux; Dart 3.0.0; PKG: pkgs/http_profile; `dart test --platform vm`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http;commands:test_3"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile;commands:test_2"
           restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http_profile
             os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: "3.0.0"
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_pub_upgrade
-        name: pkgs/http; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http
-      - name: "pkgs/http; dart test --platform chrome"
-        run: dart test --platform chrome
-        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http
-    needs:
-      - job_001
-      - job_002
-      - job_003
-      - job_004
-      - job_005
-      - job_006
-      - job_007
-  job_010:
-    name: "unit_test; linux; Dart 3.0.0; PKGS: pkgs/http, pkgs/http_profile; `dart test --platform vm`"
-    runs-on: ubuntu-latest
-    steps:
-      - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
-        with:
-          path: "~/.pub-cache/hosted"
-          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http-pkgs/http_profile;commands:test_2"
-          restore-keys: |
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0;packages:pkgs/http-pkgs/http_profile
-            os:ubuntu-latest;pub-cache-hosted;sdk:3.0.0
-            os:ubuntu-latest;pub-cache-hosted
-            os:ubuntu-latest
-      - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
-        with:
-          sdk: "3.0.0"
-      - id: checkout
-        name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
-      - id: pkgs_http_pub_upgrade
-        name: pkgs/http; dart pub upgrade
-        run: dart pub upgrade
-        if: "always() && steps.checkout.conclusion == 'success'"
-        working-directory: pkgs/http
-      - name: "pkgs/http; dart test --platform vm"
-        run: dart test --platform vm
-        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
-        working-directory: pkgs/http
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_profile_pub_upgrade
         name: pkgs/http_profile; dart pub upgrade
         run: dart pub upgrade
@@ -387,12 +323,130 @@
       - job_005
       - job_006
       - job_007
+      - job_008
+  job_010:
+    name: "unit_test; linux; Dart 3.3.0; PKG: pkgs/http; `dart run --define=no_default_http_client=true test/no_default_http_client_test.dart`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http;commands:command_1"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: "3.3.0"
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_pub_upgrade
+        name: pkgs/http; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http
+      - name: "pkgs/http; dart run --define=no_default_http_client=true test/no_default_http_client_test.dart"
+        run: "dart run --define=no_default_http_client=true test/no_default_http_client_test.dart"
+        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+      - job_005
+      - job_006
+      - job_007
+      - job_008
   job_011:
+    name: "unit_test; linux; Dart 3.3.0; PKG: pkgs/http; `dart test --platform chrome`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http;commands:test_3"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: "3.3.0"
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_pub_upgrade
+        name: pkgs/http; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http
+      - name: "pkgs/http; dart test --platform chrome"
+        run: dart test --platform chrome
+        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+      - job_005
+      - job_006
+      - job_007
+      - job_008
+  job_012:
+    name: "unit_test; linux; Dart 3.3.0; PKG: pkgs/http; `dart test --platform vm`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http;commands:test_2"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:3.3.0
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: "3.3.0"
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_pub_upgrade
+        name: pkgs/http; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http
+      - name: "pkgs/http; dart test --platform vm"
+        run: dart test --platform vm
+        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+      - job_005
+      - job_006
+      - job_007
+      - job_008
+  job_013:
     name: "unit_test; linux; Dart dev; PKG: pkgs/http; `dart run --define=no_default_http_client=true test/no_default_http_client_test.dart`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http;commands:command_1"
@@ -402,12 +456,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: dev
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_pub_upgrade
         name: pkgs/http; dart pub upgrade
         run: dart pub upgrade
@@ -425,12 +479,13 @@
       - job_005
       - job_006
       - job_007
-  job_012:
+      - job_008
+  job_014:
     name: "unit_test; linux; Dart dev; PKG: pkgs/http; `dart test --platform chrome`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http;commands:test_3"
@@ -440,12 +495,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: dev
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_pub_upgrade
         name: pkgs/http; dart pub upgrade
         run: dart pub upgrade
@@ -463,12 +518,13 @@
       - job_005
       - job_006
       - job_007
-  job_013:
+      - job_008
+  job_015:
     name: "unit_test; linux; Dart dev; PKGS: pkgs/http, pkgs/http_profile; `dart test --platform vm`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http-pkgs/http_profile;commands:test_2"
@@ -478,12 +534,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Dart SDK
-        uses: dart-lang/setup-dart@b64355ae6ca0b5d484f0106a033dd1388965d06d
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
         with:
           sdk: dev
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_http_pub_upgrade
         name: pkgs/http; dart pub upgrade
         run: dart pub upgrade
@@ -510,12 +566,52 @@
       - job_005
       - job_006
       - job_007
-  job_014:
+      - job_008
+  job_016:
+    name: "unit_test; linux; Dart dev; PKG: pkgs/http; `dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm`"
+    runs-on: ubuntu-latest
+    steps:
+      - name: Cache Pub hosted dependencies
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
+        with:
+          path: "~/.pub-cache/hosted"
+          key: "os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http;commands:test_4"
+          restore-keys: |
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev;packages:pkgs/http
+            os:ubuntu-latest;pub-cache-hosted;sdk:dev
+            os:ubuntu-latest;pub-cache-hosted
+            os:ubuntu-latest
+      - name: Setup Dart SDK
+        uses: dart-lang/setup-dart@fedb1266e91cf51be2fdb382869461a434b920a3
+        with:
+          sdk: dev
+      - id: checkout
+        name: Checkout repository
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - id: pkgs_http_pub_upgrade
+        name: pkgs/http; dart pub upgrade
+        run: dart pub upgrade
+        if: "always() && steps.checkout.conclusion == 'success'"
+        working-directory: pkgs/http
+      - name: "pkgs/http; dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm"
+        run: "dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm"
+        if: "always() && steps.pkgs_http_pub_upgrade.conclusion == 'success'"
+        working-directory: pkgs/http
+    needs:
+      - job_001
+      - job_002
+      - job_003
+      - job_004
+      - job_005
+      - job_006
+      - job_007
+      - job_008
+  job_017:
     name: "unit_test; linux; Flutter stable; PKG: pkgs/flutter_http_example; `flutter test --platform chrome`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:pkgs/flutter_http_example;commands:test_1"
@@ -525,12 +621,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -548,12 +644,13 @@
       - job_005
       - job_006
       - job_007
-  job_015:
+      - job_008
+  job_018:
     name: "unit_test; linux; Flutter stable; PKG: pkgs/flutter_http_example; `flutter test`"
     runs-on: ubuntu-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:ubuntu-latest;pub-cache-hosted;sdk:stable;packages:pkgs/flutter_http_example;commands:command_0"
@@ -563,12 +660,12 @@
             os:ubuntu-latest;pub-cache-hosted
             os:ubuntu-latest
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -586,12 +683,13 @@
       - job_005
       - job_006
       - job_007
-  job_016:
+      - job_008
+  job_019:
     name: "unit_test; macos; Flutter stable; PKG: pkgs/flutter_http_example; `flutter test`"
     runs-on: macos-latest
     steps:
       - name: Cache Pub hosted dependencies
-        uses: actions/cache@704facf57e6136b1bc63b828d79edcd491f0ee84
+        uses: actions/cache@13aacd865c20de90d75de3b17ebe84f7a17d57d2
         with:
           path: "~/.pub-cache/hosted"
           key: "os:macos-latest;pub-cache-hosted;sdk:stable;packages:pkgs/flutter_http_example;commands:command_0"
@@ -601,12 +699,12 @@
             os:macos-latest;pub-cache-hosted
             os:macos-latest
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -624,17 +722,18 @@
       - job_005
       - job_006
       - job_007
-  job_017:
+      - job_008
+  job_020:
     name: "unit_test; windows; Flutter stable; PKG: pkgs/flutter_http_example; `flutter test`"
     runs-on: windows-latest
     steps:
       - name: Setup Flutter SDK
-        uses: subosito/flutter-action@48cafc24713cca54bbe03cdc3a423187d413aafa
+        uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: stable
       - id: checkout
         name: Checkout repository
-        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608
+        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
       - id: pkgs_flutter_http_example_pub_upgrade
         name: pkgs/flutter_http_example; flutter pub upgrade
         run: flutter pub upgrade
@@ -652,3 +751,4 @@
       - job_005
       - job_006
       - job_007
+      - job_008
diff --git a/.github/workflows/java.yml b/.github/workflows/java.yml
index ec1b9da..ed3f93f 100644
--- a/.github/workflows/java.yml
+++ b/.github/workflows/java.yml
@@ -29,8 +29,8 @@
       run:
         working-directory: pkgs/java_http
     steps:
-      - uses: actions/checkout@v4
-      - uses: subosito/flutter-action@v2
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: 'stable'
 
@@ -55,8 +55,8 @@
         working-directory: pkgs/java_http
 
     steps:
-      - uses: actions/checkout@v4
-      - uses: subosito/flutter-action@v2
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
+      - uses: subosito/flutter-action@2783a3f08e1baf891508463f8c6653c258246225
         with:
           channel: 'stable'
 
diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml
new file mode 100644
index 0000000..ab1ac49
--- /dev/null
+++ b/.github/workflows/no-response.yml
@@ -0,0 +1,37 @@
+# A workflow to close issues where the author hasn't responded to a request for
+# more information; see https://github.com/actions/stale.
+
+name: No Response
+
+# Run as a daily cron.
+on:
+  schedule:
+    # Every day at 8am
+    - cron: '0 8 * * *'
+
+# All permissions not specified are set to 'none'.
+permissions:
+  issues: write
+  pull-requests: write
+
+jobs:
+  no-response:
+    runs-on: ubuntu-latest
+    if: ${{ github.repository_owner == 'dart-lang' }}
+    steps:
+      - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e
+        with:
+          # Don't automatically mark inactive issues+PRs as stale.
+          days-before-stale: -1
+          # Close needs-info issues and PRs after 14 days of inactivity.
+          days-before-close: 14
+          stale-issue-label: "needs-info"
+          close-issue-message: >
+            Without additional information we're not able to resolve this issue.
+            Feel free to add more info or respond to any questions above and we
+            can reopen the case. Thanks for your contribution!
+          stale-pr-label: "needs-info"
+          close-pr-message: >
+            Without additional information we're not able to resolve this PR.
+            Feel free to add more info or respond to any questions above.
+            Thanks for your contribution!
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..f0cc574
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,17 @@
+# A CI configuration to auto-publish pub packages.
+
+name: Publish
+
+on:
+  pull_request:
+    branches: [ master ]
+  push:
+    tags: [ '[A-z]+-v[0-9]+.[0-9]+.[0-9]+' ]
+
+jobs:
+  publish:
+    if: ${{ github.repository_owner == 'dart-lang' }}
+    uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main
+    permissions:
+      id-token: write # Required for authentication using OIDC
+      pull-requests: write # Required for writing the pull request note
diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml
index 9933aad..54e3df5 100644
--- a/.github/workflows/pull_request_label.yml
+++ b/.github/workflows/pull_request_label.yml
@@ -16,7 +16,7 @@
       pull-requests: write
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594
+      - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9
         with:
           repo-token: "${{ secrets.GITHUB_TOKEN }}"
           sync-labels: true
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..32c6edd
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+# Contributing :heart:
+
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+
+All submissions, including submissions by project members, require review.
+
+### File headers
+
+All files in the project must start with the following header.
+
+```dart
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+```
+
+### The small print
+
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
+
+## A word about conduct
+
+We pledge to maintain an open and welcoming environment :hugs:.
+For details, see our
+[code of conduct](https://dart.dev/community/code-of-conduct).
diff --git a/README.md b/README.md
index 006404d..2ce0382 100644
--- a/README.md
+++ b/README.md
@@ -15,3 +15,8 @@
 | [cronet_http](pkgs/cronet_http/) | An Android Flutter plugin that provides access to the [Cronet](https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/package-summary) HTTP client. | [![pub package](https://img.shields.io/pub/v/cronet_http.svg)](https://pub.dev/packages/cronet_http) |
 | [cupertino_http](pkgs/cupertino_http/) | A macOS/iOS Flutter plugin that provides access to the [Foundation URL Loading System](https://developer.apple.com/documentation/foundation/url_loading_system). | [![pub package](https://img.shields.io/pub/v/cupertino_http.svg)](https://pub.dev/packages/cupertino_http) |
 | [flutter_http_example](pkgs/flutter_http_example/) | An Flutter app that demonstrates how to configure and use `package:http`. | — |
+
+## Contributing
+
+If you'd like to contribute to any of these packages, see the
+[Contributing Guide](CONTRIBUTING.md).
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 7d741bd..e536739 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -14,15 +14,10 @@
   - avoid_returning_this
   - avoid_unused_constructor_parameters
   - cascade_invocations
-  - comment_references
   - join_return_with_assignment
   - missing_whitespace_between_adjacent_strings
   - no_adjacent_strings_in_list
   - no_runtimeType_toString
-  - prefer_const_constructors
   - prefer_const_declarations
   - prefer_expression_function_bodies
-  - prefer_relative_imports
-  - test_types_in_equals
   - use_string_buffers
-  - use_super_parameters
diff --git a/pkgs/cronet_http/CHANGELOG.md b/pkgs/cronet_http/CHANGELOG.md
index 83e2701..bc92551 100644
--- a/pkgs/cronet_http/CHANGELOG.md
+++ b/pkgs/cronet_http/CHANGELOG.md
@@ -1,6 +1,17 @@
+## 1.1.0
+
+* Use `package:http_image_provider` in the example application.
+* Support Android API 21+.
+* Support `BaseResponseWithUrl`.
+
+## 1.0.0
+
+* No functional changes. 
+
 ## 0.4.2
 
 * Require `package:jni >= 0.7.2` to remove a potential buffer overflow.
+* Fix a bug where incorrect HTTP request methods were sent.
 
 ## 0.4.1
  
diff --git a/pkgs/cronet_http/README.md b/pkgs/cronet_http/README.md
index 0238eb6..47ebd8b 100644
--- a/pkgs/cronet_http/README.md
+++ b/pkgs/cronet_http/README.md
@@ -1,31 +1,61 @@
+[![pub package](https://img.shields.io/pub/v/cronet_http.svg)](https://pub.dev/packages/cronet_http)
+[![package publisher](https://img.shields.io/pub/publisher/cronet_http.svg)](https://pub.dev/packages/cronet_http/publisher)
+
 An Android Flutter plugin that provides access to the
-[Cronet](https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/package-summary)
+[Cronet][]
 HTTP client.
 
 Cronet is available as part of
-[Google Play Services](https://developers.google.com/android/guides/overview). 
+[Google Play Services][]. 
 
-This package depends on
-[Google Play Services](https://developers.google.com/android/guides/overview)
-for its Cronet implementation.
+This package depends on [Google Play Services][] for its [Cronet][]
+implementation.
 [`package:cronet_http_embedded`](https://pub.dev/packages/cronet_http_embedded)
-is functionally identical to this package but embeds Cronet directly instead
-of relying on
-[Google Play Services](https://developers.google.com/android/guides/overview).
+is functionally identical to this package but embeds [Cronet][] directly
+instead of relying on [Google Play Services][].
 
-## Status: Experimental
+## Motivation
 
-**NOTE**: This package is currently experimental and published under the
-[labs.dart.dev](https://dart.dev/dart-team-packages) pub publisher in order to
-solicit feedback. 
+Using [Cronet][], rather than the socket-based [dart:io HttpClient][]
+implemententation, has several advantages:
 
-For packages in the labs.dart.dev publisher we generally plan to either graduate
-the package into a supported publisher (dart.dev, tools.dart.dev) after a period
-of feedback and iteration, or discontinue the package. These packages have a
-much higher expected rate of API and breaking changes.
+1. It automatically supports Android platform features such as HTTP proxies.
+2. It supports configurable caching.
+3. It supports more HTTP features such as HTTP/3.
 
-Your feedback is valuable and will help us evolve this package. 
-For general feedback and suggestions please comment in the
-[feedback issue](https://github.com/dart-lang/http/issues/764).
-For bugs, please file an issue in the 
-[bug tracker](https://github.com/dart-lang/http/issues).
+## Using
+
+The easiest way to use this library is via the the high-level interface
+defined by [package:http Client][].
+
+This approach allows the same HTTP code to be used on all platforms, while
+still allowing platform-specific setup.
+
+```dart
+import 'package:cronet_http/cronet_http.dart';
+import 'package:http/http.dart';
+import 'package:http/io_client.dart';
+
+void main() async {
+  final Client httpClient;
+  if (Platform.isAndroid) {
+    final engine = CronetEngine.build(
+        cacheMode: CacheMode.memory,
+        cacheMaxSize: 2 * 1024 * 1024,
+        userAgent: 'Book Agent');
+    httpClient = CronetClient.fromCronetEngine(engine);
+  } else {
+    httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
+  }
+
+  final response = await client.get(Uri.https(
+      'www.googleapis.com',
+      '/books/v1/volumes',
+      {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
+}
+```
+
+[Cronet]: https://developer.android.com/guide/topics/connectivity/cronet/reference/org/chromium/net/package-summary
+[dart:io HttpClient]: https://api.dart.dev/stable/dart-io/HttpClient-class.html
+[Google Play Services]: https://developers.google.com/android/guides/overview
+[package:http Client]: https://pub.dev/documentation/http/latest/http/Client-class.html
diff --git a/pkgs/cronet_http/android/build.gradle b/pkgs/cronet_http/android/build.gradle
index 3a91d8a..af945d9 100644
--- a/pkgs/cronet_http/android/build.gradle
+++ b/pkgs/cronet_http/android/build.gradle
@@ -2,14 +2,14 @@
 version '1.0-SNAPSHOT'
 
 buildscript {
-    ext.kotlin_version = '1.6.10'
+    ext.kotlin_version = '1.7.21'
     repositories {
         google()
         mavenCentral()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath 'com.android.tools.build:gradle:7.4.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -25,6 +25,11 @@
 apply plugin: 'kotlin-android'
 
 android {
+    // Conditional for compatibility with AGP <4.2.
+    if (project.android.hasProperty("namespace")) {
+        namespace 'io.flutter.plugins.cronet_http'
+    }
+
     compileSdkVersion 31
 
     compileOptions {
@@ -41,7 +46,11 @@
     }
 
     defaultConfig {
-        minSdkVersion 16
+        // api-level/minSdkVersion should be help in sync in:
+        // - .github/workflows/cronet.yml
+        // - pkgs/cronet_http/android/build.gradle
+        // - pkgs/cronet_http/example/android/app/build.gradle
+        minSdkVersion 21
     }
 
     defaultConfig {
@@ -56,6 +65,5 @@
 }
 
 dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
     implementation "com.google.android.gms:play-services-cronet:18.0.1"
 }
diff --git a/pkgs/cronet_http/example/android/app/build.gradle b/pkgs/cronet_http/example/android/app/build.gradle
index 88b3356..dfd7427 100644
--- a/pkgs/cronet_http/example/android/app/build.gradle
+++ b/pkgs/cronet_http/example/android/app/build.gradle
@@ -44,9 +44,11 @@
 
     defaultConfig {
         applicationId "io.flutter.cronet_http_example"
-        // You can update the following values to match your application needs.
-        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
-        minSdkVersion flutter.minSdkVersion
+        // api-level/minSdkVersion should be help in sync in:
+        // - .github/workflows/cronet.yml
+        // - pkgs/cronet_http/android/build.gradle
+        // - pkgs/cronet_http/example/android/app/build.gradle
+        minSdkVersion 21
         targetSdkVersion flutter.targetSdkVersion
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
@@ -66,7 +68,9 @@
 }
 
 dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    // TODO(#1112): org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions.
+    // This should be removed when https://github.com/flutter/flutter/issues/125062 is fixed.
+    implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
     // ""com.google.android.gms:play-services-cronet" is only present so that
     // `jnigen` will work. Applications should not include this line.
     implementation "com.google.android.gms:play-services-cronet:18.0.1"
diff --git a/pkgs/cronet_http/example/android/app/src/debug/AndroidManifest.xml b/pkgs/cronet_http/example/android/app/src/debug/AndroidManifest.xml
index b17be9f..6170951 100644
--- a/pkgs/cronet_http/example/android/app/src/debug/AndroidManifest.xml
+++ b/pkgs/cronet_http/example/android/app/src/debug/AndroidManifest.xml
@@ -1,4 +1,4 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.cronet_http_example">
+    package="io.flutter.cronet_http_example">
     <uses-permission android:name="android.permission.INTERNET"/>
 </manifest>
diff --git a/pkgs/cronet_http/example/android/app/src/main/AndroidManifest.xml b/pkgs/cronet_http/example/android/app/src/main/AndroidManifest.xml
index e8d5f3f..254760d 100644
--- a/pkgs/cronet_http/example/android/app/src/main/AndroidManifest.xml
+++ b/pkgs/cronet_http/example/android/app/src/main/AndroidManifest.xml
@@ -1,5 +1,5 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.cronet_http_example">
+    package="io.flutter.cronet_http_example">
     <uses-permission android:name="android.permission.INTERNET"/>
    <application
         android:label="cronet_http_example"
diff --git a/pkgs/cronet_http/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/pkgs/cronet_http/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
index 90015fd..3772f02 100644
--- a/pkgs/cronet_http/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
+++ b/pkgs/cronet_http/example/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -16,14 +16,24 @@
   private static final String TAG = "GeneratedPluginRegistrant";
   public static void registerWith(@NonNull FlutterEngine flutterEngine) {
     try {
-      flutterEngine.getPlugins().add(new io.flutter.plugins.cronet_http.CronetHttpPlugin());
-    } catch(Exception e) {
-      Log.e(TAG, "Error registering plugin cronet_http, io.flutter.plugins.cronet_http.CronetHttpPlugin", e);
+      flutterEngine.getPlugins().add(new dev.flutter.plugins.integration_test.IntegrationTestPlugin());
+    } catch (Exception e) {
+      Log.e(TAG, "Error registering plugin integration_test, dev.flutter.plugins.integration_test.IntegrationTestPlugin", e);
     }
     try {
-      flutterEngine.getPlugins().add(new dev.flutter.plugins.integration_test.IntegrationTestPlugin());
-    } catch(Exception e) {
-      Log.e(TAG, "Error registering plugin integration_test, dev.flutter.plugins.integration_test.IntegrationTestPlugin", e);
+      flutterEngine.getPlugins().add(new com.github.dart_lang.jni.JniPlugin());
+    } catch (Exception e) {
+      Log.e(TAG, "Error registering plugin jni, com.github.dart_lang.jni.JniPlugin", e);
+    }
+    try {
+      flutterEngine.getPlugins().add(new io.flutter.plugins.pathprovider.PathProviderPlugin());
+    } catch (Exception e) {
+      Log.e(TAG, "Error registering plugin path_provider_android, io.flutter.plugins.pathprovider.PathProviderPlugin", e);
+    }
+    try {
+      flutterEngine.getPlugins().add(new com.tekartik.sqflite.SqflitePlugin());
+    } catch (Exception e) {
+      Log.e(TAG, "Error registering plugin sqflite, com.tekartik.sqflite.SqflitePlugin", e);
     }
   }
 }
diff --git a/pkgs/cronet_http/example/android/app/src/main/kotlin/com/example/cronet_http_example/MainActivity.kt b/pkgs/cronet_http/example/android/app/src/main/kotlin/io/flutter/cronet_http_example/MainActivity.kt
similarity index 70%
rename from pkgs/cronet_http/example/android/app/src/main/kotlin/com/example/cronet_http_example/MainActivity.kt
rename to pkgs/cronet_http/example/android/app/src/main/kotlin/io/flutter/cronet_http_example/MainActivity.kt
index 2688c93..97b07c8 100644
--- a/pkgs/cronet_http/example/android/app/src/main/kotlin/com/example/cronet_http_example/MainActivity.kt
+++ b/pkgs/cronet_http/example/android/app/src/main/kotlin/io/flutter/cronet_http_example/MainActivity.kt
@@ -1,4 +1,4 @@
-package com.example.cronet_http_example
+package io.flutter.cronet_http_example
 
 import io.flutter.embedding.android.FlutterActivity
 
diff --git a/pkgs/cronet_http/example/android/app/src/profile/AndroidManifest.xml b/pkgs/cronet_http/example/android/app/src/profile/AndroidManifest.xml
index b17be9f..6170951 100644
--- a/pkgs/cronet_http/example/android/app/src/profile/AndroidManifest.xml
+++ b/pkgs/cronet_http/example/android/app/src/profile/AndroidManifest.xml
@@ -1,4 +1,4 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.cronet_http_example">
+    package="io.flutter.cronet_http_example">
     <uses-permission android:name="android.permission.INTERNET"/>
 </manifest>
diff --git a/pkgs/cronet_http/example/android/build.gradle b/pkgs/cronet_http/example/android/build.gradle
index 83ae220..954fa1c 100644
--- a/pkgs/cronet_http/example/android/build.gradle
+++ b/pkgs/cronet_http/example/android/build.gradle
@@ -1,12 +1,12 @@
 buildscript {
-    ext.kotlin_version = '1.6.10'
+    ext.kotlin_version = '1.7.21'
     repositories {
         google()
         mavenCentral()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:7.1.2'
+        classpath 'com.android.tools.build:gradle:7.4.2'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -26,6 +26,6 @@
     project.evaluationDependsOn(':app')
 }
 
-task clean(type: Delete) {
+tasks.register("clean", Delete) {
     delete rootProject.buildDir
 }
diff --git a/pkgs/cronet_http/example/android/gradle/wrapper/gradle-wrapper.properties b/pkgs/cronet_http/example/android/gradle/wrapper/gradle-wrapper.properties
index cc5527d..cfe88f6 100644
--- a/pkgs/cronet_http/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/pkgs/cronet_http/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
diff --git a/pkgs/cronet_http/example/integration_test/client_test.dart b/pkgs/cronet_http/example/integration_test/client_test.dart
index cc6b80e..f126f84 100644
--- a/pkgs/cronet_http/example/integration_test/client_test.dart
+++ b/pkgs/cronet_http/example/integration_test/client_test.dart
@@ -13,6 +13,8 @@
       () => testAll(
             CronetClient.defaultCronetEngine,
             canStreamRequestBody: false,
+            canReceiveSetCookieHeaders: true,
+            canSendCookieHeaders: true,
           ));
 
   group('from cronet engine', () {
diff --git a/pkgs/cronet_http/example/lib/book.dart b/pkgs/cronet_http/example/lib/book.dart
index 61a663b..584ae9a 100644
--- a/pkgs/cronet_http/example/lib/book.dart
+++ b/pkgs/cronet_http/example/lib/book.dart
@@ -5,7 +5,7 @@
 class Book {
   String title;
   String description;
-  String imageUrl;
+  Uri imageUrl;
 
   Book(this.title, this.description, this.imageUrl);
 
@@ -21,7 +21,7 @@
                 'description': final String description,
                 'imageLinks': {'smallThumbnail': final String thumbnail}
               }) {
-            books.add(Book(title, description, thumbnail));
+            books.add(Book(title, description, Uri.parse(thumbnail)));
           }
         }
       }
diff --git a/pkgs/cronet_http/example/lib/main.dart b/pkgs/cronet_http/example/lib/main.dart
index 61f11a7..6e1bad2 100644
--- a/pkgs/cronet_http/example/lib/main.dart
+++ b/pkgs/cronet_http/example/lib/main.dart
@@ -5,21 +5,31 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:cached_network_image/cached_network_image.dart';
 import 'package:cronet_http/cronet_http.dart';
 import 'package:flutter/material.dart';
 import 'package:http/http.dart';
+import 'package:http/io_client.dart';
+import 'package:http_image_provider/http_image_provider.dart';
+import 'package:provider/provider.dart';
 
 import 'book.dart';
 
 void main() {
-  var clientFactory = Client.new; // Constructs the default client.
+  final Client httpClient;
   if (Platform.isAndroid) {
     final engine = CronetEngine.build(
-        cacheMode: CacheMode.memory, userAgent: 'Book Agent');
-    clientFactory = () => CronetClient.fromCronetEngine(engine);
+        cacheMode: CacheMode.memory,
+        cacheMaxSize: 2 * 1024 * 1024,
+        userAgent: 'Book Agent');
+    httpClient = CronetClient.fromCronetEngine(engine);
+  } else {
+    httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
   }
-  runWithClient(() => runApp(const BookSearchApp()), clientFactory);
+
+  runApp(Provider<Client>(
+      create: (_) => httpClient,
+      child: const BookSearchApp(),
+      dispose: (_, client) => client.close()));
 }
 
 class BookSearchApp extends StatelessWidget {
@@ -44,20 +54,22 @@
 class _HomePageState extends State<HomePage> {
   List<Book>? _books;
   String? _lastQuery;
+  late Client _client;
 
   @override
   void initState() {
     super.initState();
+    _client = context.read<Client>();
   }
 
   // Get the list of books matching `query`.
   // The `get` call will automatically use the `client` configurated in `main`.
   Future<List<Book>> _findMatchingBooks(String query) async {
-    final response = await get(
+    final response = await _client.get(
       Uri.https(
         'www.googleapis.com',
         '/books/v1/volumes',
-        {'q': query, 'maxResults': '40', 'printType': 'books'},
+        {'q': query, 'maxResults': '20', 'printType': 'books'},
       ),
     );
 
@@ -129,11 +141,10 @@
         itemBuilder: (context, index) => Card(
           key: ValueKey(widget.books[index].title),
           child: ListTile(
-            leading: CachedNetworkImage(
-                placeholder: (context, url) =>
-                    const CircularProgressIndicator(),
-                imageUrl:
-                    widget.books[index].imageUrl.replaceFirst('http', 'https')),
+            leading: Image(
+                image: HttpImage(
+                    widget.books[index].imageUrl.replace(scheme: 'https'),
+                    client: context.read<Client>())),
             title: Text(widget.books[index].title),
             subtitle: Text(widget.books[index].description),
           ),
diff --git a/pkgs/cronet_http/example/pubspec.yaml b/pkgs/cronet_http/example/pubspec.yaml
index 568cef9..41c6611 100644
--- a/pkgs/cronet_http/example/pubspec.yaml
+++ b/pkgs/cronet_http/example/pubspec.yaml
@@ -4,19 +4,20 @@
 publish_to: 'none'
 
 environment:
-  sdk: "^3.0.0"
+  sdk: ^3.2.0
 
 dependencies:
-  cached_network_image: ^3.2.3
   cronet_http:
     path: ../
   cupertino_icons: ^1.0.2
   flutter:
     sdk: flutter
-  http: ^0.13.5
+  http: ^1.0.0
+  http_image_provider: ^0.0.2
+  provider: ^6.1.1
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   flutter_test:
     sdk: flutter
   http_client_conformance_tests:
diff --git a/pkgs/cronet_http/lib/src/cronet_client.dart b/pkgs/cronet_http/lib/src/cronet_client.dart
index c4d0d19..3c2a426 100644
--- a/pkgs/cronet_http/lib/src/cronet_client.dart
+++ b/pkgs/cronet_http/lib/src/cronet_client.dart
@@ -23,6 +23,21 @@
 final _digitRegex = RegExp(r'^\d+$');
 const _bufferSize = 10 * 1024; // The size of the Cronet read buffer.
 
+/// This class can be removed when `package:http` v2 is released.
+class _StreamedResponseWithUrl extends StreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  _StreamedResponseWithUrl(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.reasonPhrase});
+}
+
 /// The type of caching to use when making HTTP requests.
 enum CacheMode {
   disabled,
@@ -163,9 +178,11 @@
         case final contentLengthHeader?:
           contentLength = int.parse(contentLengthHeader);
       }
-      responseCompleter.complete(StreamedResponse(
+      responseCompleter.complete(_StreamedResponseWithUrl(
         responseStream!.stream,
         responseInfo.getHttpStatusCode(),
+        url: Uri.parse(
+            responseInfo.getUrl().toDartString(releaseOriginal: true)),
         contentLength: contentLength,
         reasonPhrase: responseInfo
             .getHttpStatusText()
@@ -317,7 +334,7 @@
       jb.UrlRequestCallbackProxy.new1(
           _urlRequestCallbacks(request, responseCompleter)),
       _executor,
-    );
+    )..setHttpMethod(request.method.toJString());
 
     var headers = request.headers;
     if (body.isNotEmpty &&
diff --git a/pkgs/cronet_http/pubspec.yaml b/pkgs/cronet_http/pubspec.yaml
index 82390c8..f1a642b 100644
--- a/pkgs/cronet_http/pubspec.yaml
+++ b/pkgs/cronet_http/pubspec.yaml
@@ -1,7 +1,7 @@
 name: cronet_http
-description: >
+version: 1.1.0
+description: >-
   An Android Flutter plugin that provides access to the Cronet HTTP client.
-version: 0.4.2
 repository: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http
 
 environment:
@@ -11,11 +11,11 @@
 dependencies:
   flutter:
     sdk: flutter
-  http: '>=0.13.4 <2.0.0'
+  http: ^1.2.0
   jni: ^0.7.2
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   jnigen: ^0.7.0
   xml: ^6.1.0
   yaml_edit: ^2.0.3
@@ -25,4 +25,3 @@
     platforms:
       android:
         ffiPlugin: true
-
diff --git a/pkgs/cronet_http/tool/prepare_for_embedded.dart b/pkgs/cronet_http/tool/prepare_for_embedded.dart
index e0f99d7..16a2c0b 100644
--- a/pkgs/cronet_http/tool/prepare_for_embedded.dart
+++ b/pkgs/cronet_http/tool/prepare_for_embedded.dart
@@ -13,6 +13,9 @@
 /// 1. Modifying the Gradle build file to reference the embedded Cronet.
 /// 2. Modifying the *name* and *description* in `pubspec.yaml`.
 /// 3. Replacing `README.md` with `README_EMBEDDED.md`.
+/// 4. Change the name of `cronet_http.dart` to `cronet_http_embedded.dart`.
+/// 5. Update all the imports from `package:cronet_http/cronet_http.dart` to
+///    `package:cronet_http_embedded/cronet_http_embedded.dart`
 ///
 /// After running this script, `flutter pub publish`
 /// can be run to update package:cronet_http_embedded.
@@ -26,6 +29,7 @@
 import 'package:xml/xml.dart';
 import 'package:yaml_edit/yaml_edit.dart';
 
+late final String _scriptName;
 late final Directory _packageDirectory;
 
 const _gmsDependencyName = 'com.google.android.gms:play-services-cronet';
@@ -39,18 +43,31 @@
   'dl.google.com',
   'android/maven2/org/chromium/net/group-index.xml',
 );
+// Finds the Google Play Services Cronet dependency line. For example:
+// '    implementation "com.google.android.gms:play-services-cronet:18.0.1"'
+final implementationRegExp = RegExp(
+  '^\\s*implementation [\'"]'
+  '$_gmsDependencyName'
+  ':\\d+.\\d+.\\d+[\'"]',
+  multiLine: true,
+);
 
-void main() async {
-  if (Directory.current.path.endsWith('tool')) {
-    _packageDirectory = Directory.current.parent;
-  } else {
-    _packageDirectory = Directory.current;
-  }
-
+void main(List<String> args) async {
+  final script = Platform.script.toFilePath();
+  _scriptName = script.split(Platform.pathSeparator).last;
+  _packageDirectory = Directory(
+    Uri.directory(
+      '${script.replaceAll(_scriptName, '')}'
+      '..${Platform.pathSeparator}',
+    ).toFilePath(),
+  );
   final latestVersion = await _getLatestCronetVersion();
-  updateCronetDependency(latestVersion);
+  updateBuildGradle(latestVersion);
+  updateExampleBuildGradle();
   updatePubSpec();
   updateReadme();
+  updateLibraryName();
+  updateImports();
 }
 
 Future<String> _getLatestCronetVersion() async {
@@ -69,37 +86,77 @@
   return versions.last;
 }
 
-/// Update android/build.gradle
-void updateCronetDependency(String latestVersion) {
-  final fBuildGradle = File('${_packageDirectory.path}/android/build.gradle');
-  final gradleContent = fBuildGradle.readAsStringSync();
-  final implementationRegExp = RegExp(
-    '^\\s*implementation [\'"]'
-    '$_gmsDependencyName'
-    ':\\d+.\\d+.\\d+[\'"]',
-    multiLine: true,
-  );
+/// Update android/build.gradle.
+void updateBuildGradle(String latestVersion) {
+  final buildGradle = File('${_packageDirectory.path}/android/build.gradle');
+  final gradleContent = buildGradle.readAsStringSync();
   final newImplementation = '$_embeddedDependencyName:$latestVersion';
-  print('Patching $newImplementation');
+  print('Updating ${buildGradle.path}: adding $newImplementation');
   final newGradleContent = gradleContent.replaceAll(
     implementationRegExp,
     '    implementation "$newImplementation"',
   );
-  fBuildGradle.writeAsStringSync(newGradleContent);
+  buildGradle.writeAsStringSync(newGradleContent);
 }
 
-/// Update pubspec.yaml
+/// Remove the cronet reference from ./example/android/app/build.gradle.
+void updateExampleBuildGradle() {
+  final buildGradle =
+      File('${_packageDirectory.path}/example/android/app/build.gradle');
+  final gradleContent = buildGradle.readAsStringSync();
+
+  print('Updating ${buildGradle.path}: removing cronet reference');
+  final newGradleContent = gradleContent.replaceAll(
+    implementationRegExp,
+    '    // NOTE: removed in package:cronet_http_embedded',
+  );
+  buildGradle.writeAsStringSync(newGradleContent);
+}
+
+/// Update pubspec.yaml and example/pubspec.yaml.
 void updatePubSpec() {
+  print('Updating pubspec.yaml');
   final fPubspec = File('${_packageDirectory.path}/pubspec.yaml');
   final yamlEditor = YamlEditor(fPubspec.readAsStringSync())
     ..update(['name'], _packageName)
     ..update(['description'], _packageDescription);
   fPubspec.writeAsStringSync(yamlEditor.toString());
+  print('Updating example/pubspec.yaml');
+  final examplePubspec = File('${_packageDirectory.path}/example/pubspec.yaml');
+  final replaced = examplePubspec
+      .readAsStringSync()
+      .replaceAll('cronet_http:', 'cronet_http_embedded:');
+  examplePubspec.writeAsStringSync(replaced);
 }
 
-/// Move README_EMBEDDED.md to replace README.md
+/// Move README_EMBEDDED.md to replace README.md.
 void updateReadme() {
+  print('Updating README.md from README_EMBEDDED.md');
   File('${_packageDirectory.path}/README.md').deleteSync();
   File('${_packageDirectory.path}/README_EMBEDDED.md')
       .renameSync('${_packageDirectory.path}/README.md');
 }
+
+void updateImports() {
+  print('Updating imports in Dart files');
+  for (final file in _packageDirectory.listSync(recursive: true)) {
+    if (file is File &&
+        file.path.endsWith('.dart') &&
+        !file.path.contains(_scriptName)) {
+      final updatedSource = file.readAsStringSync().replaceAll(
+            'package:cronet_http/cronet_http.dart',
+            'package:cronet_http_embedded/cronet_http_embedded.dart',
+          );
+      file.writeAsStringSync(updatedSource);
+    }
+  }
+}
+
+void updateLibraryName() {
+  print('Renaming cronet_http.dart to cronet_http_embedded.dart');
+  File(
+    '${_packageDirectory.path}/lib/cronet_http.dart',
+  ).renameSync(
+    '${_packageDirectory.path}/lib/cronet_http_embedded.dart',
+  );
+}
diff --git a/pkgs/cupertino_http/CHANGELOG.md b/pkgs/cupertino_http/CHANGELOG.md
index 3a69c49..34a36c5 100644
--- a/pkgs/cupertino_http/CHANGELOG.md
+++ b/pkgs/cupertino_http/CHANGELOG.md
@@ -1,3 +1,15 @@
+## 1.3.1-wip
+
+## 1.3.0
+
+* Use `package:http_image_provider` in the example application.
+* Support `BaseResponseWithUrl`.
+
+## 1.2.0
+
+* Add support for setting additional http headers in
+  `URLSessionConfiguration`.
+
 ## 1.1.0
 
 * Add websocket support to `cupertino_api`.
diff --git a/pkgs/cupertino_http/README.md b/pkgs/cupertino_http/README.md
index 83514dc..77c1d79 100644
--- a/pkgs/cupertino_http/README.md
+++ b/pkgs/cupertino_http/README.md
@@ -24,49 +24,25 @@
 still allowing platform-specific setup.
 
 ```dart
-late Client client;
-if (Platform.isIOS) {
-  final config = URLSessionConfiguration.ephemeralSessionConfiguration()
-    ..allowsCellularAccess = false
-    ..allowsConstrainedNetworkAccess = false
-    ..allowsExpensiveNetworkAccess = false;
-  client = CupertinoClient.fromSessionConfiguration(config);
-} else {
-  client = IOClient(); // Uses an HTTP client based on dart:io
-}
+import 'package:cupertino_http/cupertino_http.dart';
+import 'package:http/http.dart';
+import 'package:http/io_client.dart';
 
-final response = await client.get(Uri.https(
-    'www.googleapis.com',
-    '/books/v1/volumes',
-    {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
-```
-
-[package:http runWithClient][] can be used to configure the
-[package:http Client][] for the entire application.
-
-```dart
-void main() {
-  late Client client;
-  if (Platform.isIOS) {
-    client = CupertinoClient.defaultSessionConfiguration();
+void main() async {
+  final Client httpClient;
+  if (Platform.isIOS || Platform.isMacOS) {
+    final config = URLSessionConfiguration.ephemeralSessionConfiguration()
+      ..cache = URLCache.withCapacity(memoryCapacity: 2 * 1024 * 1024)
+      ..httpAdditionalHeaders = {'User-Agent': 'Book Agent'};
+    httpClient = CupertinoClient.fromSessionConfiguration(config);
   } else {
-    client = IOClient();
+    httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
   }
 
-  runWithClient(() => runApp(const MyApp()), () => client);
-}
-
-...
-
-class MainPageState extends State<MainPage> {
-  void someMethod() {
-    // Will use the Client configured in main.
-    final response = await get(Uri.https(
-        'www.googleapis.com',
-        '/books/v1/volumes',
-        {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
-  }
-  ...
+  final response = await client.get(Uri.https(
+      'www.googleapis.com',
+      '/books/v1/volumes',
+      {'q': 'HTTP', 'maxResults': '40', 'printType': 'books'}));
 }
 ```
 
@@ -88,6 +64,5 @@
 ```
 
 [package:http Client]: https://pub.dev/documentation/http/latest/http/Client-class.html
-[package:http runWithClient]: https://pub.dev/documentation/http/latest/http/runWithClient.html
 [Foundation URL Loading System]: https://developer.apple.com/documentation/foundation/url_loading_system
 [dart:io HttpClient]: https://api.dart.dev/stable/dart-io/HttpClient-class.html
diff --git a/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart b/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart
index a0823bf..3007123 100644
--- a/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart
+++ b/pkgs/cupertino_http/example/integration_test/client_conformance_test.dart
@@ -11,11 +11,19 @@
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
 
   group('defaultSessionConfiguration', () {
-    testAll(CupertinoClient.defaultSessionConfiguration);
+    testAll(
+      CupertinoClient.defaultSessionConfiguration,
+      canReceiveSetCookieHeaders: true,
+      canSendCookieHeaders: true,
+    );
   });
   group('fromSessionConfiguration', () {
     final config = URLSessionConfiguration.ephemeralSessionConfiguration();
-    testAll(() => CupertinoClient.fromSessionConfiguration(config),
-        canWorkInIsolates: false);
+    testAll(
+      () => CupertinoClient.fromSessionConfiguration(config),
+      canWorkInIsolates: false,
+      canReceiveSetCookieHeaders: true,
+      canSendCookieHeaders: true,
+    );
   });
 }
diff --git a/pkgs/cupertino_http/example/integration_test/client_test.dart b/pkgs/cupertino_http/example/integration_test/client_test.dart
index 9247783..1becd5c 100644
--- a/pkgs/cupertino_http/example/integration_test/client_test.dart
+++ b/pkgs/cupertino_http/example/integration_test/client_test.dart
@@ -2,6 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:async';
 import 'dart:io';
 import 'dart:typed_data';
 
@@ -45,7 +46,7 @@
       }
       final request = StreamedRequest('POST', uri);
       request.sink.add(data);
-      request.sink.close();
+      unawaited(request.sink.close());
       await client.send(request);
       expect(serverHash, sha1.convert(data).bytes);
     });
diff --git a/pkgs/cupertino_http/example/integration_test/url_session_configuration_test.dart b/pkgs/cupertino_http/example/integration_test/url_session_configuration_test.dart
index 85386bc..ab117c3 100644
--- a/pkgs/cupertino_http/example/integration_test/url_session_configuration_test.dart
+++ b/pkgs/cupertino_http/example/integration_test/url_session_configuration_test.dart
@@ -2,10 +2,38 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:io';
+
 import 'package:cupertino_http/cupertino_http.dart';
 import 'package:integration_test/integration_test.dart';
 import 'package:test/test.dart';
 
+/// Make a HTTP request using the given configuration and return the headers
+/// received by the server.
+Future<Map<String, List<String>>> sentHeaders(
+    URLSessionConfiguration config) async {
+  final session = URLSession.sessionWithConfiguration(config);
+  final headers = <String, List<String>>{};
+  final server = (await HttpServer.bind('localhost', 0))
+    ..listen((request) async {
+      request.headers.forEach((k, v) => headers[k] = v);
+      await request.drain<void>();
+      request.response.headers.set('Content-Type', 'text/plain');
+      request.response.write('Hello World');
+      await request.response.close();
+    });
+
+  final task = session.dataTaskWithRequest(URLRequest.fromUrl(
+      Uri(scheme: 'http', host: 'localhost', port: server.port)))
+    ..resume();
+  while (task.state != URLSessionTaskState.urlSessionTaskStateCompleted) {
+    await pumpEventQueue();
+  }
+
+  await server.close();
+  return headers;
+}
+
 void testProperties(URLSessionConfiguration config) {
   group('properties', () {
     test('allowsCellularAccess', () {
@@ -32,6 +60,22 @@
       config.discretionary = false;
       expect(config.discretionary, false);
     });
+    test('httpAdditionalHeaders', () async {
+      expect(config.httpAdditionalHeaders, isNull);
+
+      config.httpAdditionalHeaders = {
+        'User-Agent': 'My Client',
+        'MyHeader': 'myvalue'
+      };
+      expect(config.httpAdditionalHeaders,
+          {'User-Agent': 'My Client', 'MyHeader': 'myvalue'});
+      final headers = await sentHeaders(config);
+      expect(headers, containsPair('user-agent', ['My Client']));
+      expect(headers, containsPair('myheader', ['myvalue']));
+
+      config.httpAdditionalHeaders = null;
+      expect(config.httpAdditionalHeaders, isNull);
+    });
     test('httpCookieAcceptPolicy', () {
       config.httpCookieAcceptPolicy =
           HTTPCookieAcceptPolicy.httpCookieAcceptPolicyAlways;
diff --git a/pkgs/cupertino_http/example/lib/book.dart b/pkgs/cupertino_http/example/lib/book.dart
index f2a27fc..b47ca9e 100644
--- a/pkgs/cupertino_http/example/lib/book.dart
+++ b/pkgs/cupertino_http/example/lib/book.dart
@@ -5,7 +5,7 @@
 class Book {
   String title;
   String description;
-  String imageUrl;
+  Uri imageUrl;
 
   Book(this.title, this.description, this.imageUrl);
 
@@ -21,7 +21,7 @@
                 'description': final String description,
                 'imageLinks': {'smallThumbnail': final String thumbnail}
               }) {
-            books.add(Book(title, description, thumbnail));
+            books.add(Book(title, description, Uri.parse(thumbnail)));
           }
         }
       }
diff --git a/pkgs/cupertino_http/example/lib/main.dart b/pkgs/cupertino_http/example/lib/main.dart
index edfceac..092300a 100644
--- a/pkgs/cupertino_http/example/lib/main.dart
+++ b/pkgs/cupertino_http/example/lib/main.dart
@@ -5,19 +5,30 @@
 import 'dart:convert';
 import 'dart:io';
 
-import 'package:cached_network_image/cached_network_image.dart';
 import 'package:cupertino_http/cupertino_http.dart';
 import 'package:flutter/material.dart';
 import 'package:http/http.dart';
+import 'package:http/io_client.dart';
+import 'package:http_image_provider/http_image_provider.dart';
+import 'package:provider/provider.dart';
 
 import 'book.dart';
 
 void main() {
-  var clientFactory = Client.new; // The default Client.
+  final Client httpClient;
   if (Platform.isIOS || Platform.isMacOS) {
-    clientFactory = CupertinoClient.defaultSessionConfiguration.call;
+    final config = URLSessionConfiguration.ephemeralSessionConfiguration()
+      ..cache = URLCache.withCapacity(memoryCapacity: 2 * 1024 * 1024)
+      ..httpAdditionalHeaders = {'User-Agent': 'Book Agent'};
+    httpClient = CupertinoClient.fromSessionConfiguration(config);
+  } else {
+    httpClient = IOClient(HttpClient()..userAgent = 'Book Agent');
   }
-  runWithClient(() => runApp(const BookSearchApp()), clientFactory);
+
+  runApp(Provider<Client>(
+      create: (_) => httpClient,
+      child: const BookSearchApp(),
+      dispose: (_, client) => client.close()));
 }
 
 class BookSearchApp extends StatelessWidget {
@@ -42,20 +53,22 @@
 class _HomePageState extends State<HomePage> {
   List<Book>? _books;
   String? _lastQuery;
+  late Client _client;
 
   @override
   void initState() {
     super.initState();
+    _client = context.read<Client>();
   }
 
   // Get the list of books matching `query`.
   // The `get` call will automatically use the `client` configurated in `main`.
   Future<List<Book>> _findMatchingBooks(String query) async {
-    final response = await get(
+    final response = await _client.get(
       Uri.https(
         'www.googleapis.com',
         '/books/v1/volumes',
-        {'q': query, 'maxResults': '40', 'printType': 'books'},
+        {'q': query, 'maxResults': '20', 'printType': 'books'},
       ),
     );
 
@@ -127,11 +140,10 @@
         itemBuilder: (context, index) => Card(
           key: ValueKey(widget.books[index].title),
           child: ListTile(
-            leading: CachedNetworkImage(
-                placeholder: (context, url) =>
-                    const CircularProgressIndicator(),
-                imageUrl:
-                    widget.books[index].imageUrl.replaceFirst('http', 'https')),
+            leading: Image(
+                image: HttpImage(
+                    widget.books[index].imageUrl.replace(scheme: 'https'),
+                    client: context.read<Client>())),
             title: Text(widget.books[index].title),
             subtitle: Text(widget.books[index].description),
           ),
diff --git a/pkgs/cupertino_http/example/pubspec.yaml b/pkgs/cupertino_http/example/pubspec.yaml
index be72731..8d61e62 100644
--- a/pkgs/cupertino_http/example/pubspec.yaml
+++ b/pkgs/cupertino_http/example/pubspec.yaml
@@ -6,22 +6,23 @@
 version: 1.0.0+1
 
 environment:
-  sdk: ^3.0.0
-  flutter: '>=3.10.0'
+  sdk: ^3.2.0
+  flutter: '>=3.16.0'
 
 dependencies:
-  cached_network_image: ^3.2.3
   cupertino_http:
     path: ../
   cupertino_icons: ^1.0.2
   flutter:
     sdk: flutter
-  http: ^0.13.5
+  http: ^1.0.0
+  http_image_provider: ^0.0.2
+  provider: ^6.1.1
 
 dev_dependencies:
   convert: ^3.1.1
   crypto: ^3.0.3
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   ffi: ^2.0.1
   flutter_test:
     sdk: flutter
diff --git a/pkgs/cupertino_http/lib/src/cupertino_api.dart b/pkgs/cupertino_http/lib/src/cupertino_api.dart
index 01c4853..5cebd7e 100644
--- a/pkgs/cupertino_http/lib/src/cupertino_api.dart
+++ b/pkgs/cupertino_http/lib/src/cupertino_api.dart
@@ -344,6 +344,32 @@
   bool get discretionary => _nsObject.discretionary;
   set discretionary(bool value) => _nsObject.discretionary = value;
 
+  /// Additional headers to send with each request.
+  ///
+  /// Note that the getter for this field returns a **copy** of the headers.
+  ///
+  /// See [NSURLSessionConfiguration.HTTPAdditionalHeaders](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411532-httpadditionalheaders)
+  Map<String, String>? get httpAdditionalHeaders {
+    if (_nsObject.HTTPAdditionalHeaders case var additionalHeaders?) {
+      final headers = ncb.NSDictionary.castFrom(additionalHeaders);
+      return stringDictToMap(headers);
+    }
+    return null;
+  }
+
+  set httpAdditionalHeaders(Map<String, String>? headers) {
+    if (headers == null) {
+      _nsObject.HTTPAdditionalHeaders = null;
+      return;
+    }
+    final d = ncb.NSMutableDictionary.alloc(linkedLibs).init();
+    headers.forEach((key, value) {
+      d.setObject_forKey_(
+          value.toNSString(linkedLibs), key.toNSString(linkedLibs));
+    });
+    _nsObject.HTTPAdditionalHeaders = d;
+  }
+
   /// What policy to use when deciding whether to accept cookies.
   ///
   /// See [NSURLSessionConfiguration.HTTPCookieAcceptPolicy](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1408933-httpcookieacceptpolicy).
@@ -441,6 +467,7 @@
       'allowsConstrainedNetworkAccess=$allowsConstrainedNetworkAccess '
       'allowsExpensiveNetworkAccess=$allowsExpensiveNetworkAccess '
       'discretionary=$discretionary '
+      'httpAdditionalHeaders=$httpAdditionalHeaders '
       'httpCookieAcceptPolicy=$httpCookieAcceptPolicy '
       'httpShouldSetCookies=$httpShouldSetCookies '
       'httpMaximumConnectionsPerHost=$httpMaximumConnectionsPerHost '
diff --git a/pkgs/cupertino_http/lib/src/cupertino_client.dart b/pkgs/cupertino_http/lib/src/cupertino_client.dart
index a4c7715..b93dd5c 100644
--- a/pkgs/cupertino_http/lib/src/cupertino_client.dart
+++ b/pkgs/cupertino_http/lib/src/cupertino_client.dart
@@ -17,11 +17,27 @@
 
 final _digitRegex = RegExp(r'^\d+$');
 
+/// This class can be removed when `package:http` v2 is released.
+class _StreamedResponseWithUrl extends StreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  _StreamedResponseWithUrl(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.reasonPhrase});
+}
+
 class _TaskTracker {
   final responseCompleter = Completer<URLResponse>();
   final BaseRequest request;
   final responseController = StreamController<Uint8List>();
   int numRedirects = 0;
+  Uri? lastUrl; // The last URL redirected to.
 
   _TaskTracker(this.request);
 
@@ -180,6 +196,7 @@
     ++taskTracker.numRedirects;
     if (taskTracker.request.followRedirects &&
         taskTracker.numRedirects <= taskTracker.request.maxRedirects) {
+      taskTracker.lastUrl = request.url;
       return request;
     }
     return null;
@@ -292,9 +309,10 @@
       );
     }
 
-    return StreamedResponse(
+    return _StreamedResponseWithUrl(
       taskTracker.responseController.stream,
       response.statusCode,
+      url: taskTracker.lastUrl ?? request.url,
       contentLength: response.expectedContentLength == -1
           ? null
           : response.expectedContentLength,
diff --git a/pkgs/cupertino_http/pubspec.yaml b/pkgs/cupertino_http/pubspec.yaml
index 9d639a8..2a819e1 100644
--- a/pkgs/cupertino_http/pubspec.yaml
+++ b/pkgs/cupertino_http/pubspec.yaml
@@ -1,23 +1,23 @@
 name: cupertino_http
-description: >
+version: 1.3.1-wip
+description: >-
   A macOS/iOS Flutter plugin that provides access to the Foundation URL
   Loading System.
-version: 1.1.0
 repository: https://github.com/dart-lang/http/tree/master/pkgs/cupertino_http
 
 environment:
-  sdk: ^3.0.0
-  flutter: '>=3.10.0'  # If changed, update test matrix.
+  sdk: ^3.2.0
+  flutter: '>=3.16.0'  # If changed, update test matrix.
 
 dependencies:
   async: ^2.5.0
   ffi: ^2.1.0
   flutter:
     sdk: flutter
-  http: '>=0.13.4 <2.0.0'
+  http: ^1.2.0
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   ffigen: ^9.0.1
 
 flutter:
diff --git a/pkgs/flutter_http_example/README.md b/pkgs/flutter_http_example/README.md
index 2c6cdfd..1a9cf9d 100644
--- a/pkgs/flutter_http_example/README.md
+++ b/pkgs/flutter_http_example/README.md
@@ -9,8 +9,7 @@
   including:
 
     * configuration for multiple platforms.
-    * using `runWithClient` and `package:provider` to pass `Client`s through
-      an application.
+    * using `package:provider` to pass `Client`s through an application.
     * writing tests using `MockClient`.
 
 ## The important bits
@@ -34,9 +33,8 @@
 
 * import `http_client_factory.dart` or `http_client_factory_web.dart`,
   depending on whether we are targeting the web browser or not.
-* share a `package:http` `Client` by using `runWithClient` and
-  `package:provider`.
-* call `package:http` functions.
+* share a `package:http` `Client` by using `package:provider`.
+* call `package:http` `Client` methods.
 
 ### `widget_test.dart`
 
diff --git a/pkgs/flutter_http_example/lib/book.dart b/pkgs/flutter_http_example/lib/book.dart
index f2a27fc..b47ca9e 100644
--- a/pkgs/flutter_http_example/lib/book.dart
+++ b/pkgs/flutter_http_example/lib/book.dart
@@ -5,7 +5,7 @@
 class Book {
   String title;
   String description;
-  String imageUrl;
+  Uri imageUrl;
 
   Book(this.title, this.description, this.imageUrl);
 
@@ -21,7 +21,7 @@
                 'description': final String description,
                 'imageLinks': {'smallThumbnail': final String thumbnail}
               }) {
-            books.add(Book(title, description, thumbnail));
+            books.add(Book(title, description, Uri.parse(thumbnail)));
           }
         }
       }
diff --git a/pkgs/flutter_http_example/lib/http_client_factory.dart b/pkgs/flutter_http_example/lib/http_client_factory.dart
index cb36597..6e6ddc0 100644
--- a/pkgs/flutter_http_example/lib/http_client_factory.dart
+++ b/pkgs/flutter_http_example/lib/http_client_factory.dart
@@ -7,13 +7,23 @@
 import 'package:cronet_http/cronet_http.dart';
 import 'package:cupertino_http/cupertino_http.dart';
 import 'package:http/http.dart';
+import 'package:http/io_client.dart';
+
+const _maxCacheSize = 2 * 1024 * 1024;
 
 Client httpClient() {
   if (Platform.isAndroid) {
-    return CronetClient.defaultCronetEngine();
+    final engine = CronetEngine.build(
+        cacheMode: CacheMode.memory,
+        cacheMaxSize: _maxCacheSize,
+        userAgent: 'Book Agent');
+    return CronetClient.fromCronetEngine(engine);
   }
   if (Platform.isIOS || Platform.isMacOS) {
-    return CupertinoClient.defaultSessionConfiguration();
+    final config = URLSessionConfiguration.ephemeralSessionConfiguration()
+      ..cache = URLCache.withCapacity(memoryCapacity: _maxCacheSize)
+      ..httpAdditionalHeaders = {'User-Agent': 'Book Agent'};
+    return CupertinoClient.fromSessionConfiguration(config);
   }
-  return Client(); // Return the default client.
+  return IOClient(HttpClient()..userAgent = 'Book Agent');
 }
diff --git a/pkgs/flutter_http_example/lib/main.dart b/pkgs/flutter_http_example/lib/main.dart
index 70f003c..548c3de 100644
--- a/pkgs/flutter_http_example/lib/main.dart
+++ b/pkgs/flutter_http_example/lib/main.dart
@@ -4,32 +4,20 @@
 
 import 'dart:convert';
 
-import 'package:cached_network_image/cached_network_image.dart';
 import 'package:flutter/material.dart';
 import 'package:http/http.dart';
+import 'package:http_image_provider/http_image_provider.dart';
 import 'package:provider/provider.dart';
 
 import 'book.dart';
 import 'http_client_factory.dart'
-    if (dart.library.html) 'http_client_factory_web.dart' as http_factory;
+    if (dart.library.js_interop) 'http_client_factory_web.dart' as http_factory;
 
 void main() {
-  // `runWithClient` is used to control which `package:http` `Client` is used
-  // when the `Client` constructor is called. This method allows you to choose
-  // the `Client` even when the package that you are using does not offer
-  // explicit parameterization.
-  //
-  // However, `runWithClient` does not work with Flutter tests. See
-  // https://github.com/flutter/flutter/issues/96939.
-  //
-  // Use `package:provider` and `runWithClient` together so that tests and
-  // unparameterized `Client` usages both work.
-  runWithClient(
-      () => runApp(Provider<Client>(
-          create: (_) => http_factory.httpClient(),
-          child: const BookSearchApp(),
-          dispose: (_, client) => client.close())),
-      http_factory.httpClient);
+  runApp(Provider<Client>(
+      create: (_) => http_factory.httpClient(),
+      child: const BookSearchApp(),
+      dispose: (_, client) => client.close()));
 }
 
 class BookSearchApp extends StatelessWidget {
@@ -141,11 +129,10 @@
         itemBuilder: (context, index) => Card(
           key: ValueKey(widget.books[index].title),
           child: ListTile(
-            leading: CachedNetworkImage(
-                placeholder: (context, url) =>
-                    const CircularProgressIndicator(),
-                imageUrl:
-                    widget.books[index].imageUrl.replaceFirst('http', 'https')),
+            leading: Image(
+                image: HttpImage(
+                    widget.books[index].imageUrl.replace(scheme: 'https'),
+                    client: context.read<Client>())),
             title: Text(widget.books[index].title),
             subtitle: Text(widget.books[index].description),
           ),
diff --git a/pkgs/flutter_http_example/pubspec.yaml b/pkgs/flutter_http_example/pubspec.yaml
index 6f3d1ba..7d0f089 100644
--- a/pkgs/flutter_http_example/pubspec.yaml
+++ b/pkgs/flutter_http_example/pubspec.yaml
@@ -1,27 +1,26 @@
 name: flutter_http_example
+version: 1.0.0
 description: Demonstrates how to use package:http in a Flutter app.
 
 publish_to: 'none'
 
-version: 1.0.0
-
 environment:
   sdk: ^3.0.0
   flutter: '>=3.10.0'
 
 dependencies:
-  cached_network_image: ^3.2.3
-  cronet_http: ^0.4.1
-  cupertino_http: ^1.1.0
+  cronet_http: ^1.0.0
+  cupertino_http: ^1.2.0
   cupertino_icons: ^1.0.2
   fetch_client: ^1.0.2
   flutter:
     sdk: flutter
-  http: ^0.13.5
+  http: ^1.0.0
+  http_image_provider: ^0.0.2
   provider: ^6.0.5
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   flutter_test:
     sdk: flutter
   integration_test:
diff --git a/pkgs/flutter_http_example/test/widget_test.dart b/pkgs/flutter_http_example/test/widget_test.dart
index 9e0af25..f890a1b 100644
--- a/pkgs/flutter_http_example/test/widget_test.dart
+++ b/pkgs/flutter_http_example/test/widget_test.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 import 'package:flutter/material.dart';
 import 'package:flutter_http_example/main.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -17,7 +19,7 @@
         "title": "Flutter Cookbook",
         "description": "Write, test, and publish your web, desktop...",
         "imageLinks": {
-          "smallThumbnail": "http://books.google.com/books/content?id=gcnAEAAAQBAJ&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api"
+          "smallThumbnail": "http://thumbnailurl/"
         }
       }
     }
@@ -25,6 +27,11 @@
 }
 ''';
 
+final _dummyPngImage = base64Decode(
+  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmM'
+  'IQAAAABJRU5ErkJggg==',
+);
+
 void main() {
   Widget app(Client client) => Provider<Client>(
       create: (_) => client,
@@ -42,11 +49,14 @@
 
   testWidgets('test search with one result', (WidgetTester tester) async {
     final mockClient = MockClient((request) async {
-      if (request.url.path != '/books/v1/volumes' &&
-          request.url.queryParameters['q'] != 'Flutter') {
-        return Response('', 404);
+      if (request.url.path == '/books/v1/volumes' &&
+          request.url.queryParameters['q'] == 'Flutter') {
+        return Response(_singleBookResponse, 200);
+      } else if (request.url == Uri.https('thumbnailurl', '/')) {
+        return Response.bytes(_dummyPngImage, 200,
+            headers: const {'Content-Type': 'image/png'});
       }
-      return Response(_singleBookResponse, 200);
+      return Response('', 404);
     });
 
     await tester.pumpWidget(app(mockClient));
diff --git a/pkgs/http/CHANGELOG.md b/pkgs/http/CHANGELOG.md
index 6d39b10..86bc678 100644
--- a/pkgs/http/CHANGELOG.md
+++ b/pkgs/http/CHANGELOG.md
@@ -1,8 +1,26 @@
+## 1.2.1
+
+* Require Dart `^3.3`
+* Require `package:web` `^0.5.0`.
+
+## 1.2.0
+
+* Add `MockClient.pngResponse`, which makes it easier to fake image responses.
+* Added the ability to fetch the URL of the response through `BaseResponseWithUrl`.
+* Add the ability to get headers as a `Map<String, List<String>` to
+  `BaseResponse`.
+
+## 1.1.2
+
+* Allow `web: '>=0.3.0 <0.5.0'`.
+
 ## 1.1.1
 
 * `BrowserClient` throws `ClientException` when the `'Content-Length'` header
   is invalid.
 * `IOClient` trims trailing whitespace on header values.
+* Require Dart 3.2
+* Browser: support Wasm by using `package:web`.
 
 ## 1.1.0
 
diff --git a/pkgs/http/README.md b/pkgs/http/README.md
index c889f2b..85ec291 100644
--- a/pkgs/http/README.md
+++ b/pkgs/http/README.md
@@ -4,8 +4,8 @@
 A composable, Future-based library for making HTTP requests.
 
 This package contains a set of high-level functions and classes that make it
-easy to consume HTTP resources. It's multi-platform, and supports mobile, desktop,
-and the browser.
+easy to consume HTTP resources. It's multi-platform (mobile, desktop, and
+browser) and supports multiple implementations.
 
 ## Using
 
@@ -23,6 +23,11 @@
 print(await http.read(Uri.https('example.com', 'foobar.txt')));
 ```
 
+> [!NOTE]
+> Flutter applications may require
+> [additional configuration](https://docs.flutter.dev/data-and-backend/networking#platform-notes)
+> to make HTTP requests.
+
 If you're making multiple requests to the same server, you can keep open a
 persistent connection by using a [Client][] rather than making one-off requests.
 If you do this, make sure to close the client when you're done:
@@ -41,6 +46,12 @@
 }
 ```
 
+> [!TIP]
+> For detailed background information and practical usage examples, see:
+> - [Dart Development: Fetch data from the internet](https://dart.dev/tutorials/server/fetch-data)
+> - [Flutter Cookbook: Fetch data from the internet](https://docs.flutter.dev/cookbook/networking/fetch-data)
+> - [The Flutter HTTP example application][flutterhttpexample]
+
 You can also exert more fine-grained control over your requests and responses by
 creating [Request][] or [StreamedRequest][] objects yourself and passing them to
 [Client.send][].
@@ -100,3 +111,182 @@
 the [`RetryClient()`][new RetryClient] constructor.
 
 [new RetryClient]: https://pub.dev/documentation/http/latest/retry/RetryClient/RetryClient.html
+
+## Choosing an implementation
+
+There are multiple implementations of the `package:http` [`Client`][client] interface. By default, `package:http` uses [`BrowserClient`][browserclient] on the web and [`IOClient`][ioclient] on all other platforms. You an choose a different [`Client`][client] implementation based on the needs of your application.
+
+You can change implementations without changing your application code, except
+for a few lines of [configuration](#2-configure-the-http-client).
+
+Some well supported implementations are:
+
+| Implementation | Supported Platforms | SDK | Caching | HTTP3/QUIC | Platform Native | 
+| -------------- | ------------------- | ----| ------- | ---------- | --------------- |
+| `package:http` — [`IOClient`][ioclient] | Android, iOS, Linux, macOS, Windows | Dart, Flutter | ❌ | ❌ | ❌ |
+| `package:http` — [`BrowserClient`][browserclient] | Web | Dart, Flutter | ― | ✅︎ | ✅︎ | Dart, Flutter |
+| [`package:cupertino_http`][cupertinohttp] — [`CupertinoClient`][cupertinoclient] | iOS, macOS | Flutter | ✅︎ | ✅︎ | ✅︎ |
+| [`package:cronet_http`][cronethttp] — [`CronetClient`][cronetclient] | Android | Flutter | ✅︎ | ✅︎ | ― |
+| [`package:fetch_client`][fetch] — [`FetchClient`][fetchclient] | Web | Dart, Flutter | ✅︎ | ✅︎ | ✅︎ |
+
+> [!TIP]
+> If you are writing a Dart package or Flutter pluggin that uses
+> `package:http`, you should not depend on a particular [`Client`][client]
+> implementation. Let the application author decide what implementation is
+> best for their project. You can make that easier by accepting an explicit
+> [`Client`][client] argument. For example:
+>
+> ```dart
+> Future<Album> fetchAlbum({Client? client}) async {
+>   client ??= Client();
+>   ...
+> }
+> ```
+
+## Configuration
+
+To use a HTTP client implementation other than the default, you must:
+1. Add the HTTP client as a dependency.
+2. Configure the HTTP client.
+3. Connect the HTTP client to the code that uses it.
+
+### 1. Add the HTTP client as a dependency.
+
+To add a package compatible with the Dart SDK to your project, use `dart pub add`.
+
+For example:
+
+```terminal
+# Replace  "fetch_client" with the package that you want to use.
+dart pub add fetch_client
+```
+
+To add a package that requires the Flutter SDK, use `flutter pub add`.
+
+For example:
+
+```terminal
+# Replace  "cupertino_http" with the package that you want to use.
+flutter pub add cupertino_http
+```
+
+### 2. Configure the HTTP client.
+
+Different `package:http` [`Client`][client] implementations may require
+different configuration options.
+
+Add a function that returns a correctly configured [`Client`][client]. You can
+return a different [`Client`][client] on different platforms.
+
+For example:
+
+```dart
+Client httpClient() {
+  if (Platform.isAndroid) {
+    final engine = CronetEngine.build(
+        cacheMode: CacheMode.memory,
+        cacheMaxSize: 1000000);
+    return CronetClient.fromCronetEngine(engine);
+  }
+  if (Platform.isIOS || Platform.isMacOS) {
+    final config = URLSessionConfiguration.ephemeralSessionConfiguration()
+      ..cache = URLCache.withCapacity(memoryCapacity: 1000000);
+    return CupertinoClient.fromSessionConfiguration(config);
+  }
+  return IOClient();
+}
+```
+
+> [!TIP]
+> [The Flutter HTTP example application][flutterhttpexample] demonstrates
+> configuration best practices.
+
+#### Supporting browser and native
+
+If your application can be run in the browser and natively, you must put your
+browser and native configurations in seperate files and import the correct file
+based on the platform.
+
+For example:
+
+```dart
+// -- http_client_factory.dart
+Client httpClient() {
+  if (Platform.isAndroid) {
+    return CronetClient.defaultCronetEngine();
+  }
+  if (Platform.isIOS || Platform.isMacOS) {
+    return CupertinoClient.defaultSessionConfiguration();
+  }
+  return IOClient();
+}
+```
+
+```dart
+// -- http_client_factory_web.dart
+Client httpClient() => FetchClient();
+```
+
+```dart
+// -- main.dart
+import 'http_client_factory.dart'
+    if (dart.library.js_interop) 'http_client_factory_web.dart'
+
+// The correct `httpClient` will be available.
+```
+
+### 3. Connect the HTTP client to the code that uses it.
+
+The best way to pass [`Client`][client] to the places that use it is
+explicitly through arguments.
+
+For example:
+
+```dart
+void main() {
+  final client = httpClient();
+  fetchAlbum(client, ...);
+}
+```
+
+In Flutter, you can use a one of many
+[state mangement approaches][flutterstatemanagement].
+
+If you depend on code that uses top-level functions (e.g. `http.post`) or
+calls the [`Client()`][clientconstructor] constructor, then you can use
+[`runWithClient`](runwithclient) to ensure that the correct
+`Client` is used. When an [Isolate][isolate] is spawned, it does not inherit
+any variables from the calling Zone, so `runWithClient` needs to be used in
+each Isolate that uses `package:http`.
+
+You can ensure that only the `Client` that you have explicitly configured is
+used by defining `no_default_http_client=true` in the environment. This will
+also allow the default `Client` implementation to be removed, resulting in
+a reduced application size.
+
+```terminal
+$ flutter build appbundle --dart-define=no_default_http_client=true ...
+$ dart compile exe --define=no_default_http_client=true ...
+```
+
+> [!TIP]
+> [The Flutter HTTP example application][flutterhttpexample] demonstrates
+> how to make the configured [`Client`][client] available using
+> [`package:provider`][provider] and [`package:http_image_provider`][http_image_provider].
+
+[browserclient]: https://pub.dev/documentation/http/latest/browser_client/BrowserClient-class.html
+[client]: https://pub.dev/documentation/http/latest/http/Client-class.html
+[clientconstructor]: https://pub.dev/documentation/http/latest/http/Client/Client.html
+[cupertinohttp]: https://pub.dev/packages/cupertino_http
+[cupertinoclient]: https://pub.dev/documentation/cupertino_http/latest/cupertino_http/CupertinoClient-class.html
+[cronethttp]: https://pub.dev/packages/cronet_http
+[cronetclient]: https://pub.dev/documentation/cronet_http/latest/cronet_http/CronetClient-class.html
+[fetch]: https://pub.dev/packages/fetch_client
+[fetchclient]: https://pub.dev/documentation/fetch_client/latest/fetch_client/FetchClient-class.html
+[flutterhttpexample]: https://github.com/dart-lang/http/tree/master/pkgs/flutter_http_example
+[http_image_provider]: https://pub.dev/documentation/http_image_provider
+[ioclient]: https://pub.dev/documentation/http/latest/io_client/IOClient-class.html
+[isolate]: https://dart.dev/language/concurrency#how-isolates-work
+[flutterstatemanagement]: https://docs.flutter.dev/data-and-backend/state-mgmt/options
+[provider]: https://pub.dev/packages/provider
+[runwithclient]: https://pub.dev/documentation/http/latest/http/runWithClient.html
diff --git a/pkgs/http/lib/http.dart b/pkgs/http/lib/http.dart
index 6200424..da35b23 100644
--- a/pkgs/http/lib/http.dart
+++ b/pkgs/http/lib/http.dart
@@ -16,7 +16,8 @@
 
 export 'src/base_client.dart';
 export 'src/base_request.dart';
-export 'src/base_response.dart';
+export 'src/base_response.dart'
+    show BaseResponse, BaseResponseWithUrl, HeadersWithSplitValues;
 export 'src/byte_stream.dart';
 export 'src/client.dart' hide zoneClient;
 export 'src/exception.dart';
@@ -25,7 +26,7 @@
 export 'src/request.dart';
 export 'src/response.dart';
 export 'src/streamed_request.dart';
-export 'src/streamed_response.dart';
+export 'src/streamed_response.dart' show StreamedResponse;
 
 /// Sends an HTTP HEAD request with the given headers to the given URL.
 ///
diff --git a/pkgs/http/lib/src/base_request.dart b/pkgs/http/lib/src/base_request.dart
index 70a7869..4b165c7 100644
--- a/pkgs/http/lib/src/base_request.dart
+++ b/pkgs/http/lib/src/base_request.dart
@@ -132,13 +132,25 @@
     try {
       var response = await client.send(this);
       var stream = onDone(response.stream, client.close);
-      return StreamedResponse(ByteStream(stream), response.statusCode,
-          contentLength: response.contentLength,
-          request: response.request,
-          headers: response.headers,
-          isRedirect: response.isRedirect,
-          persistentConnection: response.persistentConnection,
-          reasonPhrase: response.reasonPhrase);
+
+      if (response case BaseResponseWithUrl(:final url)) {
+        return StreamedResponseV2(ByteStream(stream), response.statusCode,
+            contentLength: response.contentLength,
+            request: response.request,
+            headers: response.headers,
+            isRedirect: response.isRedirect,
+            url: url,
+            persistentConnection: response.persistentConnection,
+            reasonPhrase: response.reasonPhrase);
+      } else {
+        return StreamedResponse(ByteStream(stream), response.statusCode,
+            contentLength: response.contentLength,
+            request: response.request,
+            headers: response.headers,
+            isRedirect: response.isRedirect,
+            persistentConnection: response.persistentConnection,
+            reasonPhrase: response.reasonPhrase);
+      }
     } catch (_) {
       client.close();
       rethrow;
diff --git a/pkgs/http/lib/src/base_response.dart b/pkgs/http/lib/src/base_response.dart
index ed95f6c..0527461 100644
--- a/pkgs/http/lib/src/base_response.dart
+++ b/pkgs/http/lib/src/base_response.dart
@@ -4,6 +4,9 @@
 
 import 'base_client.dart';
 import 'base_request.dart';
+import 'client.dart';
+import 'response.dart';
+import 'streamed_response.dart';
 
 /// The base class for HTTP responses.
 ///
@@ -43,10 +46,12 @@
   /// // values = ['Apple', 'Banana', 'Grape']
   /// ```
   ///
+  /// To retrieve the header values as a `List<String>`, use
+  /// [HeadersWithSplitValues.headersSplitValues].
+  ///
   /// If a header value contains whitespace then that whitespace may be replaced
   /// by a single space. Leading and trailing whitespace in header values are
   /// always removed.
-  // TODO(nweiz): make this a HttpHeaders object.
   final Map<String, String> headers;
 
   final bool isRedirect;
@@ -68,3 +73,99 @@
     }
   }
 }
+
+/// A [BaseResponse] with a [url] field.
+///
+/// [Client] methods that return a [BaseResponse] subclass, such as [Response]
+/// or [StreamedResponse], **may** return a [BaseResponseWithUrl].
+///
+/// For example:
+///
+/// ```dart
+/// final client = Client();
+/// final response = client.get(Uri.https('example.com', '/'));
+/// Uri? finalUri;
+/// if (response case BaseResponseWithUrl(:final url)) {
+///   finalUri = url;
+/// }
+/// // Do something with `finalUri`.
+/// client.close();
+/// ```
+///
+/// [url] will be added to [BaseResponse] when `package:http` version 2 is
+/// released and this mixin will be deprecated.
+abstract interface class BaseResponseWithUrl implements BaseResponse {
+  /// The [Uri] of the response returned by the server.
+  ///
+  /// If no redirects were followed, [url] will be the same as the requested
+  /// [Uri].
+  ///
+  /// If redirects were followed, [url] will be the [Uri] of the last redirect
+  /// that was followed.
+  abstract final Uri url;
+}
+
+/// "token" as defined in RFC 2616, 2.2
+/// See https://datatracker.ietf.org/doc/html/rfc2616#section-2.2
+const _tokenChars = r"!#$%&'*+\-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`"
+    'abcdefghijklmnopqrstuvwxyz|~';
+
+/// Splits comma-seperated header values.
+var _headerSplitter = RegExp(r'[ \t]*,[ \t]*');
+
+/// Splits comma-seperated "Set-Cookie" header values.
+///
+/// Set-Cookie strings can contain commas. In particular, the following
+/// productions defined in RFC-6265, section 4.1.1:
+/// - <sane-cookie-date> e.g. "Expires=Sun, 06 Nov 1994 08:49:37 GMT"
+/// - <path-value> e.g. "Path=somepath,"
+/// - <extension-av> e.g. "AnyString,Really,"
+///
+/// Some values are ambiguous e.g.
+/// "Set-Cookie: lang=en; Path=/foo/"
+/// "Set-Cookie: SID=x23"
+/// and:
+/// "Set-Cookie: lang=en; Path=/foo/,SID=x23"
+/// would both be result in `response.headers` => "lang=en; Path=/foo/,SID=x23"
+///
+/// The idea behind this regex is that ",<valid token>=" is more likely to
+/// start a new <cookie-pair> then be part of <path-value> or <extension-av>.
+///
+/// See https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.1
+var _setCookieSplitter = RegExp(r'[ \t]*,[ \t]*(?=[' + _tokenChars + r']+=)');
+
+extension HeadersWithSplitValues on BaseResponse {
+  /// The HTTP headers returned by the server.
+  ///
+  /// The header names are converted to lowercase and stored with their
+  /// associated header values.
+  ///
+  /// Cookies can be parsed using the dart:io `Cookie` class:
+  ///
+  /// ```dart
+  /// import "dart:io";
+  /// import "package:http/http.dart";
+  ///
+  /// void main() async {
+  /// final response = await Client().get(Uri.https('example.com', '/'));
+  /// final cookies = [
+  ///   for (var value i
+  ///       in response.headersSplitValues['set-cookie'] ?? <String>[])
+  ///     Cookie.fromSetCookieValue(value)
+  /// ];
+  Map<String, List<String>> get headersSplitValues {
+    var headersWithFieldLists = <String, List<String>>{};
+    headers.forEach((key, value) {
+      if (!value.contains(',')) {
+        headersWithFieldLists[key] = [value];
+      } else {
+        if (key == 'set-cookie') {
+          headersWithFieldLists[key] = value.split(_setCookieSplitter);
+        } else {
+          headersWithFieldLists[key] = value.split(_headerSplitter);
+        }
+      }
+    });
+    return headersWithFieldLists;
+  }
+}
diff --git a/pkgs/http/lib/src/browser_client.dart b/pkgs/http/lib/src/browser_client.dart
index 9345be0..07e2323 100644
--- a/pkgs/http/lib/src/browser_client.dart
+++ b/pkgs/http/lib/src/browser_client.dart
@@ -3,8 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:html';
-import 'dart:typed_data';
+import 'dart:js_interop';
+
+import 'package:web/web.dart';
 
 import 'base_client.dart';
 import 'base_request.dart';
@@ -25,8 +26,8 @@
   return BrowserClient();
 }
 
-/// A `dart:html`-based HTTP client that runs in the browser and is backed by
-/// XMLHttpRequests.
+/// A `package:web`-based HTTP client that runs in the browser and is backed by
+/// [XMLHttpRequest].
 ///
 /// This client inherits some of the limitations of XMLHttpRequest. It ignores
 /// the [BaseRequest.contentLength], [BaseRequest.persistentConnection],
@@ -37,7 +38,7 @@
   /// The currently active XHRs.
   ///
   /// These are aborted if the client is closed.
-  final _xhrs = <HttpRequest>{};
+  final _xhrs = <XMLHttpRequest>{};
 
   /// Whether to send credentials such as cookies or authorization headers for
   /// cross-site requests.
@@ -55,30 +56,36 @@
           'HTTP request failed. Client is already closed.', request.url);
     }
     var bytes = await request.finalize().toBytes();
-    var xhr = HttpRequest();
+    var xhr = XMLHttpRequest();
     _xhrs.add(xhr);
     xhr
-      ..open(request.method, '${request.url}', async: true)
+      ..open(request.method, '${request.url}', true)
       ..responseType = 'arraybuffer'
       ..withCredentials = withCredentials;
-    request.headers.forEach(xhr.setRequestHeader);
+    for (var header in request.headers.entries) {
+      xhr.setRequestHeader(header.key, header.value);
+    }
 
     var completer = Completer<StreamedResponse>();
 
     unawaited(xhr.onLoad.first.then((_) {
-      if (xhr.responseHeaders['content-length'] case final contentLengthHeader?
-          when !_digitRegex.hasMatch(contentLengthHeader)) {
+      if (xhr.responseHeaders['content-length'] case final contentLengthHeader
+          when contentLengthHeader != null &&
+              !_digitRegex.hasMatch(contentLengthHeader)) {
         completer.completeError(ClientException(
           'Invalid content-length header [$contentLengthHeader].',
           request.url,
         ));
         return;
       }
-      var body = (xhr.response as ByteBuffer).asUint8List();
-      completer.complete(StreamedResponse(
-          ByteStream.fromBytes(body), xhr.status!,
+      var body = (xhr.response as JSArrayBuffer).toDart.asUint8List();
+      var responseUrl = xhr.responseURL;
+      var url = responseUrl.isNotEmpty ? Uri.parse(responseUrl) : request.url;
+      completer.complete(StreamedResponseV2(
+          ByteStream.fromBytes(body), xhr.status,
           contentLength: body.length,
           request: request,
+          url: url,
           headers: xhr.responseHeaders,
           reasonPhrase: xhr.statusText));
     }));
@@ -91,7 +98,7 @@
           StackTrace.current);
     }));
 
-    xhr.send(bytes);
+    xhr.send(bytes.toJS);
 
     try {
       return await completer.future;
@@ -112,3 +119,30 @@
     _xhrs.clear();
   }
 }
+
+extension on XMLHttpRequest {
+  Map<String, String> get responseHeaders {
+    // from Closure's goog.net.Xhrio.getResponseHeaders.
+    var headers = <String, String>{};
+    var headersString = getAllResponseHeaders();
+    var headersList = headersString.split('\r\n');
+    for (var header in headersList) {
+      if (header.isEmpty) {
+        continue;
+      }
+
+      var splitIdx = header.indexOf(': ');
+      if (splitIdx == -1) {
+        continue;
+      }
+      var key = header.substring(0, splitIdx).toLowerCase();
+      var value = header.substring(splitIdx + 2);
+      if (headers.containsKey(key)) {
+        headers[key] = '${headers[key]}, $value';
+      } else {
+        headers[key] = value;
+      }
+    }
+    return headers;
+  }
+}
diff --git a/pkgs/http/lib/src/client.dart b/pkgs/http/lib/src/client.dart
index 9bceb88..7429ca8 100644
--- a/pkgs/http/lib/src/client.dart
+++ b/pkgs/http/lib/src/client.dart
@@ -12,7 +12,7 @@
 import 'base_client.dart';
 import 'base_request.dart';
 import 'client_stub.dart'
-    if (dart.library.html) 'browser_client.dart'
+    if (dart.library.js_interop) 'browser_client.dart'
     if (dart.library.io) 'io_client.dart';
 import 'exception.dart';
 import 'response.dart';
@@ -37,7 +37,8 @@
   /// Creates a new platform appropriate client.
   ///
   /// Creates an `IOClient` if `dart:io` is available and a `BrowserClient` if
-  /// `dart:html` is available, otherwise it will throw an unsupported error.
+  /// `dart:js_interop` is available, otherwise it will throw an unsupported
+  /// error.
   factory Client() => zoneClient ?? createClient();
 
   /// Sends an HTTP HEAD request with the given headers to the given URL.
diff --git a/pkgs/http/lib/src/client_stub.dart b/pkgs/http/lib/src/client_stub.dart
index 1a34d50..6384fd0 100644
--- a/pkgs/http/lib/src/client_stub.dart
+++ b/pkgs/http/lib/src/client_stub.dart
@@ -6,4 +6,4 @@
 
 /// Implemented in `browser_client.dart` and `io_client.dart`.
 BaseClient createClient() => throw UnsupportedError(
-    'Cannot create a client without dart:html or dart:io.');
+    'Cannot create a client without dart:js_interop or dart:io.');
diff --git a/pkgs/http/lib/src/io_client.dart b/pkgs/http/lib/src/io_client.dart
index 247cc8c..fe4834b 100644
--- a/pkgs/http/lib/src/io_client.dart
+++ b/pkgs/http/lib/src/io_client.dart
@@ -6,6 +6,7 @@
 
 import 'base_client.dart';
 import 'base_request.dart';
+import 'base_response.dart';
 import 'client.dart';
 import 'exception.dart';
 import 'io_streamed_response.dart';
@@ -46,6 +47,22 @@
   String toString() => 'ClientException with $cause, uri=$uri';
 }
 
+class _IOStreamedResponseV2 extends IOStreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  _IOStreamedResponseV2(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.persistentConnection,
+      super.reasonPhrase,
+      super.inner});
+}
+
 /// A `dart:io`-based HTTP [Client].
 ///
 /// If there is a socket-level failure when communicating with the server
@@ -72,6 +89,18 @@
   /// The underlying `dart:io` HTTP client.
   HttpClient? _inner;
 
+  /// Create a new `dart:io`-based HTTP [Client].
+  ///
+  /// If [inner] is provided then it can be used to provide configuration
+  /// options for the client.
+  ///
+  /// For example:
+  /// ```dart
+  /// final httpClient = HttpClient()
+  ///    ..userAgent = 'Book Agent'
+  ///    ..idleTimeout = const Duration(seconds: 5);
+  /// final client = IOClient(httpClient);
+  /// ```
   IOClient([HttpClient? inner]) : _inner = inner ?? HttpClient();
 
   /// Sends an HTTP request and asynchronously returns the response.
@@ -104,7 +133,7 @@
         headers[key] = values.map((value) => value.trimRight()).join(',');
       });
 
-      return IOStreamedResponse(
+      return _IOStreamedResponseV2(
           response.handleError((Object error) {
             final httpException = error as HttpException;
             throw ClientException(httpException.message, httpException.uri);
@@ -115,6 +144,9 @@
           request: request,
           headers: headers,
           isRedirect: response.isRedirect,
+          url: response.redirects.isNotEmpty
+              ? response.redirects.last.location
+              : request.url,
           persistentConnection: response.persistentConnection,
           reasonPhrase: response.reasonPhrase,
           inner: response);
diff --git a/pkgs/http/lib/src/mock_client.dart b/pkgs/http/lib/src/mock_client.dart
index bf2df40..52f108a 100644
--- a/pkgs/http/lib/src/mock_client.dart
+++ b/pkgs/http/lib/src/mock_client.dart
@@ -2,6 +2,8 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 import 'base_client.dart';
 import 'base_request.dart';
 import 'byte_stream.dart';
@@ -10,6 +12,11 @@
 import 'streamed_request.dart';
 import 'streamed_response.dart';
 
+final _pngImageData = base64Decode(
+  'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDw'
+  'AEhQGAhKmMIQAAAABJRU5ErkJggg==',
+);
+
 // TODO(nweiz): once Dart has some sort of Rack- or WSGI-like standard for
 // server APIs, MockClient should conform to it.
 
@@ -69,6 +76,17 @@
     var bodyStream = request.finalize();
     return await _handler(request, bodyStream);
   }
+
+  /// Return a response containing a PNG image.
+  static Response pngResponse({BaseRequest? request}) {
+    final headers = {
+      'content-type': 'image/png',
+      'content-length': '${_pngImageData.length}'
+    };
+
+    return Response.bytes(_pngImageData, 200,
+        request: request, headers: headers, reasonPhrase: 'OK');
+  }
 }
 
 /// A handler function that receives [StreamedRequest]s and sends
diff --git a/pkgs/http/lib/src/streamed_response.dart b/pkgs/http/lib/src/streamed_response.dart
index 8cc0c76..44389d7 100644
--- a/pkgs/http/lib/src/streamed_response.dart
+++ b/pkgs/http/lib/src/streamed_response.dart
@@ -26,3 +26,20 @@
       super.reasonPhrase})
       : stream = toByteStream(stream);
 }
+
+/// This class is private to `package:http` and will be removed when
+/// `package:http` v2 is released.
+class StreamedResponseV2 extends StreamedResponse
+    implements BaseResponseWithUrl {
+  @override
+  final Uri url;
+
+  StreamedResponseV2(super.stream, super.statusCode,
+      {required this.url,
+      super.contentLength,
+      super.request,
+      super.headers,
+      super.isRedirect,
+      super.persistentConnection,
+      super.reasonPhrase});
+}
diff --git a/pkgs/http/mono_pkg.yaml b/pkgs/http/mono_pkg.yaml
index 0e2f9d8..06f79d9 100644
--- a/pkgs/http/mono_pkg.yaml
+++ b/pkgs/http/mono_pkg.yaml
@@ -18,3 +18,5 @@
   - command: dart run --define=no_default_http_client=true test/no_default_http_client_test.dart
     os:
     - linux
+  - test: --test-randomize-ordering-seed=random -p chrome -c dart2wasm
+    sdk: dev
diff --git a/pkgs/http/pubspec.yaml b/pkgs/http/pubspec.yaml
index ec23e2d..1874d5c 100644
--- a/pkgs/http/pubspec.yaml
+++ b/pkgs/http/pubspec.yaml
@@ -1,18 +1,19 @@
 name: http
-version: 1.1.1-wip
+version: 1.2.1
 description: A composable, multi-platform, Future-based API for HTTP requests.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/http
 
 environment:
-  sdk: ^3.0.0
+  sdk: ^3.3.0
 
 dependencies:
   async: ^2.5.0
   http_parser: ^4.0.0
   meta: ^1.3.0
+  web: ^0.5.0
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   fake_async: ^1.2.0
   http_client_conformance_tests:
     path: ../http_client_conformance_tests/
diff --git a/pkgs/http/test/html/utils.dart b/pkgs/http/test/html/utils.dart
index abe5808..11170e1 100644
--- a/pkgs/http/test/html/utils.dart
+++ b/pkgs/http/test/html/utils.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:html';
+import 'package:web/web.dart';
 
 export '../utils.dart';
 
diff --git a/pkgs/http/test/io/client_conformance_test.dart b/pkgs/http/test/io/client_conformance_test.dart
index 5d8f7f5..65368e5 100644
--- a/pkgs/http/test/io/client_conformance_test.dart
+++ b/pkgs/http/test/io/client_conformance_test.dart
@@ -10,5 +10,9 @@
 import 'package:test/test.dart';
 
 void main() {
-  testAll(IOClient.new);
+  testAll(
+    IOClient.new, preservesMethodCase: false, // https://dartbug.com/54187
+    canReceiveSetCookieHeaders: true,
+    canSendCookieHeaders: true,
+  );
 }
diff --git a/pkgs/http/test/io/request_test.dart b/pkgs/http/test/io/request_test.dart
index ac6b44c..226781f 100644
--- a/pkgs/http/test/io/request_test.dart
+++ b/pkgs/http/test/io/request_test.dart
@@ -46,15 +46,22 @@
     final response = await request.send();
 
     expect(response.statusCode, equals(302));
+    expect(
+        response,
+        isA<http.BaseResponseWithUrl>()
+            .having((r) => r.url, 'url', serverUrl.resolve('/redirect')));
   });
 
   test('with redirects', () async {
     final request = http.Request('GET', serverUrl.resolve('/redirect'));
     final response = await request.send();
-
     expect(response.statusCode, equals(200));
     final bytesString = await response.stream.bytesToString();
     expect(bytesString, parse(containsPair('path', '/')));
+    expect(
+        response,
+        isA<http.BaseResponseWithUrl>()
+            .having((r) => r.url, 'url', serverUrl.resolve('/')));
   });
 
   test('exceeding max redirects', () async {
diff --git a/pkgs/http/test/mock_client_test.dart b/pkgs/http/test/mock_client_test.dart
index db561c5..625285c 100644
--- a/pkgs/http/test/mock_client_test.dart
+++ b/pkgs/http/test/mock_client_test.dart
@@ -5,6 +5,7 @@
 import 'dart:convert';
 
 import 'package:http/http.dart' as http;
+import 'package:http/src/request.dart';
 import 'package:http/testing.dart';
 import 'package:test/test.dart';
 
@@ -43,4 +44,25 @@
     expect(await client.read(Uri.http('example.com', '/foo')),
         equals('you did it'));
   });
+
+  test('pngResponse with default options', () {
+    final response = MockClient.pngResponse();
+    expect(response.statusCode, 200);
+    expect(response.bodyBytes.take(8),
+        [137, 80, 78, 71, 13, 10, 26, 10] // PNG header
+        );
+    expect(response.request, null);
+    expect(response.headers, containsPair('content-type', 'image/png'));
+  });
+
+  test('pngResponse with request', () {
+    final request = Request('GET', Uri.https('example.com'));
+    final response = MockClient.pngResponse(request: request);
+    expect(response.statusCode, 200);
+    expect(response.bodyBytes.take(8),
+        [137, 80, 78, 71, 13, 10, 26, 10] // PNG header
+        );
+    expect(response.request, request);
+    expect(response.headers, containsPair('content-type', 'image/png'));
+  });
 }
diff --git a/pkgs/http/test/response_test.dart b/pkgs/http/test/response_test.dart
index 38061c1..1bd9fd8 100644
--- a/pkgs/http/test/response_test.dart
+++ b/pkgs/http/test/response_test.dart
@@ -70,4 +70,73 @@
       expect(response.bodyBytes, equals([104, 101, 108, 108, 111]));
     });
   });
+
+  group('.headersSplitValues', () {
+    test('no headers', () async {
+      var response = http.Response('Hello, world!', 200);
+      expect(response.headersSplitValues, const <String, List<String>>{});
+    });
+
+    test('one header', () async {
+      var response =
+          http.Response('Hello, world!', 200, headers: {'fruit': 'apple'});
+      expect(response.headersSplitValues, const {
+        'fruit': ['apple']
+      });
+    });
+
+    test('two headers', () async {
+      var response = http.Response('Hello, world!', 200,
+          headers: {'fruit': 'apple,banana'});
+      expect(response.headersSplitValues, const {
+        'fruit': ['apple', 'banana']
+      });
+    });
+
+    test('two headers with lots of spaces', () async {
+      var response = http.Response('Hello, world!', 200,
+          headers: {'fruit': 'apple   \t   ,  \tbanana'});
+      expect(response.headersSplitValues, const {
+        'fruit': ['apple', 'banana']
+      });
+    });
+
+    test('one set-cookie', () async {
+      var response = http.Response('Hello, world!', 200, headers: {
+        'set-cookie': 'id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT'
+      });
+      expect(response.headersSplitValues, const {
+        'set-cookie': ['id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT']
+      });
+    });
+
+    test('two set-cookie, with comma in expires', () async {
+      var response = http.Response('Hello, world!', 200, headers: {
+        // ignore: missing_whitespace_between_adjacent_strings
+        'set-cookie': 'id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT,'
+            'sessionId=e8bb43229de9; Domain=foo.example.com'
+      });
+      expect(response.headersSplitValues, const {
+        'set-cookie': [
+          'id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT',
+          'sessionId=e8bb43229de9; Domain=foo.example.com'
+        ]
+      });
+    });
+
+    test('two set-cookie, with lots of commas', () async {
+      var response = http.Response('Hello, world!', 200, headers: {
+        'set-cookie':
+            // ignore: missing_whitespace_between_adjacent_strings
+            'id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/,,HE,=L=LO,'
+                'sessionId=e8bb43229de9; Domain=foo.example.com'
+      });
+      expect(response.headersSplitValues, const {
+        'set-cookie': [
+          'id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Path=/,,HE,=L=LO',
+          'sessionId=e8bb43229de9; Domain=foo.example.com'
+        ]
+      });
+    });
+  });
 }
diff --git a/pkgs/http_client_conformance_tests/bin/generate_server_wrappers.dart b/pkgs/http_client_conformance_tests/bin/generate_server_wrappers.dart
index 74f9d00..6e86737 100644
--- a/pkgs/http_client_conformance_tests/bin/generate_server_wrappers.dart
+++ b/pkgs/http_client_conformance_tests/bin/generate_server_wrappers.dart
@@ -10,12 +10,17 @@
 
 import 'package:dart_style/dart_style.dart';
 
-const vm = '''// Generated by generate_server_wrappers.dart. Do not edit.
+const _export = '''export 'server_queue_helpers.dart'
+    show StreamQueueOfNullableObjectExtension;''';
+
+const _vm = '''// Generated by generate_server_wrappers.dart. Do not edit.
 
 import 'package:stream_channel/stream_channel.dart';
 
 import '<server_file_placeholder>';
 
+$_export
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
@@ -24,11 +29,13 @@
 }
 ''';
 
-const web = '''// Generated by generate_server_wrappers.dart. Do not edit.
+const _web = '''// Generated by generate_server_wrappers.dart. Do not edit.
 
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+$_export
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
@@ -41,11 +48,11 @@
 
   files.where((file) => file.path.endsWith('_server.dart')).forEach((file) {
     final vmPath = file.path.replaceAll('_server.dart', '_server_vm.dart');
-    File(vmPath).writeAsStringSync(formatter.format(vm.replaceAll(
+    File(vmPath).writeAsStringSync(formatter.format(_vm.replaceAll(
         '<server_file_placeholder>', file.uri.pathSegments.last)));
 
     final webPath = file.path.replaceAll('_server.dart', '_server_web.dart');
-    File(webPath).writeAsStringSync(formatter.format(web.replaceAll(
+    File(webPath).writeAsStringSync(formatter.format(_web.replaceAll(
         '<server_file_placeholder>', file.uri.pathSegments.last)));
   });
 }
diff --git a/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart b/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart
index 3d00319..07903b5 100644
--- a/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/http_client_conformance_tests.dart
@@ -11,9 +11,12 @@
 import 'src/redirect_tests.dart';
 import 'src/request_body_streamed_tests.dart';
 import 'src/request_body_tests.dart';
+import 'src/request_cookies_test.dart';
 import 'src/request_headers_tests.dart';
+import 'src/request_methods_tests.dart';
 import 'src/response_body_streamed_test.dart';
 import 'src/response_body_tests.dart';
+import 'src/response_cookies_test.dart';
 import 'src/response_headers_tests.dart';
 import 'src/response_status_line_tests.dart';
 import 'src/server_errors_test.dart';
@@ -26,9 +29,12 @@
 export 'src/redirect_tests.dart' show testRedirect;
 export 'src/request_body_streamed_tests.dart' show testRequestBodyStreamed;
 export 'src/request_body_tests.dart' show testRequestBody;
+export 'src/request_cookies_test.dart' show testRequestCookies;
 export 'src/request_headers_tests.dart' show testRequestHeaders;
+export 'src/request_methods_tests.dart' show testRequestMethods;
 export 'src/response_body_streamed_test.dart' show testResponseBodyStreamed;
 export 'src/response_body_tests.dart' show testResponseBody;
+export 'src/response_cookies_test.dart' show testResponseCookies;
 export 'src/response_headers_tests.dart' show testResponseHeaders;
 export 'src/response_status_line_tests.dart' show testResponseStatusLine;
 export 'src/server_errors_test.dart' show testServerErrors;
@@ -49,14 +55,28 @@
 /// If [canWorkInIsolates] is `false` then tests that require that the [Client]
 /// work in Isolates other than the main isolate will be skipped.
 ///
+/// If [preservesMethodCase] is `false` then tests that assume that the
+/// [Client] preserves custom request method casing will be skipped.
+///
+/// If [canSendCookieHeaders] is `false` then tests that require that "cookie"
+/// headers be sent by the client will not be run.
+///
+/// If [canReceiveSetCookieHeaders] is `false` then tests that require that
+/// "set-cookie" headers be received by the client will not be run.
+///
 /// The tests are run against a series of HTTP servers that are started by the
 /// tests. If the tests are run in the browser, then the test servers are
 /// started in another process. Otherwise, the test servers are run in-process.
-void testAll(Client Function() clientFactory,
-    {bool canStreamRequestBody = true,
-    bool canStreamResponseBody = true,
-    bool redirectAlwaysAllowed = false,
-    bool canWorkInIsolates = true}) {
+void testAll(
+  Client Function() clientFactory, {
+  bool canStreamRequestBody = true,
+  bool canStreamResponseBody = true,
+  bool redirectAlwaysAllowed = false,
+  bool canWorkInIsolates = true,
+  bool preservesMethodCase = false,
+  bool canSendCookieHeaders = false,
+  bool canReceiveSetCookieHeaders = false,
+}) {
   testRequestBody(clientFactory());
   testRequestBodyStreamed(clientFactory(),
       canStreamRequestBody: canStreamRequestBody);
@@ -65,6 +85,7 @@
   testResponseBodyStreamed(clientFactory(),
       canStreamResponseBody: canStreamResponseBody);
   testRequestHeaders(clientFactory());
+  testRequestMethods(clientFactory(), preservesMethodCase: preservesMethodCase);
   testResponseHeaders(clientFactory());
   testResponseStatusLine(clientFactory());
   testRedirect(clientFactory(), redirectAlwaysAllowed: redirectAlwaysAllowed);
@@ -73,4 +94,8 @@
   testMultipleClients(clientFactory);
   testClose(clientFactory);
   testIsolate(clientFactory, canWorkInIsolates: canWorkInIsolates);
+  testRequestCookies(clientFactory(),
+      canSendCookieHeaders: canSendCookieHeaders);
+  testResponseCookies(clientFactory(),
+      canReceiveSetCookieHeaders: canReceiveSetCookieHeaders);
 }
diff --git a/pkgs/http_client_conformance_tests/lib/src/close_tests.dart b/pkgs/http_client_conformance_tests/lib/src/close_tests.dart
index 040b338..39324ad 100644
--- a/pkgs/http_client_conformance_tests/lib/src/close_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/close_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'request_body_server_vm.dart'
-    if (dart.library.html) 'request_body_server_web.dart';
+    if (dart.library.js_interop) 'request_body_server_web.dart';
 
 /// Tests that the [Client] correctly implements [Client.close].
 void testClose(Client Function() clientFactory) {
@@ -20,7 +20,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_vm.dart
index 2bb2c16..a5ae1e0 100644
--- a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'compressed_response_body_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_web.dart
index f880799..7b1d1a6 100644
--- a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_tests.dart b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_tests.dart
index 3ce871b..538b3ba 100644
--- a/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/compressed_response_body_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'compressed_response_body_server_vm.dart'
-    if (dart.library.html) 'compressed_response_body_server_web.dart';
+    if (dart.library.js_interop) 'compressed_response_body_server_web.dart';
 
 /// Tests that the [Client] correctly implements HTTP responses with compressed
 /// bodies.
@@ -32,7 +32,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart b/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart
index b4ac8b2..1723ab5 100644
--- a/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/isolate_test.dart
@@ -2,7 +2,7 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
-import 'dart:isolate' if (dart.library.html) 'dummy_isolate.dart';
+import 'dart:isolate' if (dart.library.js_interop) 'dummy_isolate.dart';
 
 import 'package:async/async.dart';
 import 'package:http/http.dart';
@@ -10,7 +10,7 @@
 import 'package:test/test.dart';
 
 import 'request_body_server_vm.dart'
-    if (dart.library.html) 'request_body_server_web.dart';
+    if (dart.library.js_interop) 'request_body_server_web.dart';
 
 Future<void> _testPost(Client Function() clientFactory, String host) async {
   await Isolate.run(
@@ -31,7 +31,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_vm.dart
index c689212..f00f4ba 100644
--- a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'multiple_clients_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_web.dart
index 91cfc76..3f71aa7 100644
--- a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_tests.dart b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_tests.dart
index be9c4d9..ad40d4a 100644
--- a/pkgs/http_client_conformance_tests/lib/src/multiple_clients_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/multiple_clients_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'multiple_clients_server_vm.dart'
-    if (dart.library.html) 'multiple_clients_server_web.dart';
+    if (dart.library.js_interop) 'multiple_clients_server_web.dart';
 
 /// Tests that the [Client] works correctly if there are many used
 /// simultaneously.
@@ -21,7 +21,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/redirect_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/redirect_server_vm.dart
index 7f9cf8c..4a9450a 100644
--- a/pkgs/http_client_conformance_tests/lib/src/redirect_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/redirect_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'redirect_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/redirect_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/redirect_server_web.dart
index 0fbe8a3..a5fb0f2 100644
--- a/pkgs/http_client_conformance_tests/lib/src/redirect_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/redirect_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart b/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
index e21a5c2..a33d077 100644
--- a/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/redirect_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'redirect_server_vm.dart'
-    if (dart.library.html) 'redirect_server_web.dart';
+    if (dart.library.js_interop) 'redirect_server_web.dart';
 
 /// Tests that the [Client] correctly implements HTTP redirect logic.
 ///
@@ -23,16 +23,30 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
+    test('no redirect', () async {
+      final request = Request('GET', Uri.http(host, '/'))
+        ..followRedirects = false;
+      final response = await client.send(request);
+      expect(response.statusCode, 200);
+      expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
+    });
+
     test('disallow redirect', () async {
       final request = Request('GET', Uri.http(host, '/1'))
         ..followRedirects = false;
       final response = await client.send(request);
       expect(response.statusCode, 302);
       expect(response.isRedirect, true);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/1'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('disallow redirect, 0 maxRedirects', () async {
@@ -42,6 +56,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 302);
       expect(response.isRedirect, true);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/1'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('allow redirect', () async {
@@ -50,6 +67,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 200);
       expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
     });
 
     test('allow redirect, 0 maxRedirects', () async {
@@ -69,6 +89,9 @@
       final response = await client.send(request);
       expect(response.statusCode, 200);
       expect(response.isRedirect, false);
+      if (response case BaseResponseWithUrl(url: final url)) {
+        expect(url, Uri.http(host, '/'));
+      }
     }, skip: redirectAlwaysAllowed ? 'redirects always allowed' : false);
 
     test('too many redirects', () async {
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_server_vm.dart
index 2260766..d2e1e4a 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'request_body_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_server_web.dart
index 250bd52..6b6ab00 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_vm.dart
index 9f58119..c343d68 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'request_body_streamed_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_web.dart
index 97e8fbc..41477ee 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_tests.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_tests.dart
index 560858d..0f43505 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_streamed_tests.dart
@@ -11,7 +11,7 @@
 import 'package:test/test.dart';
 
 import 'request_body_streamed_server_vm.dart'
-    if (dart.library.html) 'request_body_streamed_server_web.dart';
+    if (dart.library.js_interop) 'request_body_streamed_server_web.dart';
 
 /// Tests that the [Client] correctly implements streamed request body
 /// uploading.
@@ -29,7 +29,7 @@
     setUp(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDown(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_body_tests.dart b/pkgs/http_client_conformance_tests/lib/src/request_body_tests.dart
index e211da5..901f7f7 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_body_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_body_tests.dart
@@ -10,7 +10,7 @@
 import 'package:test/test.dart';
 
 import 'request_body_server_vm.dart'
-    if (dart.library.html) 'request_body_server_web.dart';
+    if (dart.library.js_interop) 'request_body_server_web.dart';
 
 class _Plus2Decoder extends Converter<List<int>, String> {
   @override
@@ -47,7 +47,7 @@
     setUp(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDown(() => httpServerChannel.sink.add(null));
 
@@ -260,6 +260,47 @@
       expect(serverReceivedBody.codeUnits, <int>[]);
     });
 
+    test('client.send() with persistentConnection', () async {
+      // Do five requests to verify that the connection persistance logic is
+      // correct.
+      for (var i = 0; i < 5; ++i) {
+        final request = Request('POST', Uri.http(host, ''))
+          ..headers['Content-Type'] = 'text/plain; charset=utf-8'
+          ..persistentConnection = true
+          ..body = 'Hello World $i';
+
+        final response = await client.send(request);
+        expect(response.statusCode, 200);
+
+        final serverReceivedContentType = await httpServerQueue.next;
+        final serverReceivedBody = await httpServerQueue.next as String;
+
+        expect(serverReceivedContentType, ['text/plain; charset=utf-8']);
+        expect(serverReceivedBody, 'Hello World $i');
+      }
+    });
+
+    test('client.send() with persistentConnection and body >64K', () async {
+      // 64KiB is special for the HTTP network API:
+      // https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
+      // See https://github.com/dart-lang/http/issues/977
+      final body = ''.padLeft(64 * 1024, 'XYZ');
+
+      final request = Request('POST', Uri.http(host, ''))
+        ..headers['Content-Type'] = 'text/plain; charset=utf-8'
+        ..persistentConnection = true
+        ..body = body;
+
+      final response = await client.send(request);
+      expect(response.statusCode, 200);
+
+      final serverReceivedContentType = await httpServerQueue.next;
+      final serverReceivedBody = await httpServerQueue.next as String;
+
+      expect(serverReceivedContentType, ['text/plain; charset=utf-8']);
+      expect(serverReceivedBody, body);
+    });
+
     test('client.send() GET with non-empty stream', () async {
       final request = StreamedRequest('GET', Uri.http(host, ''));
       request.headers['Content-Type'] = 'image/png';
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_cookies_server.dart b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server.dart
new file mode 100644
index 0000000..44653a7
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server.dart
@@ -0,0 +1,55 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:stream_channel/stream_channel.dart';
+
+/// Starts an HTTP server that captures "cookie" headers.
+///
+/// Channel protocol:
+///    On Startup:
+///     - send port
+///    On Request Received:
+///     - send a list of header lines starting with "cookie:"
+///    When Receive Anything:
+///     - exit
+void hybridMain(StreamChannel<Object?> channel) async {
+  late ServerSocket server;
+
+  server = (await ServerSocket.bind('localhost', 0))
+    ..listen((Socket socket) async {
+      final request = utf8.decoder.bind(socket).transform(const LineSplitter());
+
+      final cookies = <String>[];
+      request.listen((line) {
+        if (line.toLowerCase().startsWith('cookie:')) {
+          cookies.add(line);
+        }
+
+        if (line.isEmpty) {
+          // A blank line indicates the end of the headers.
+          channel.sink.add(cookies);
+        }
+      });
+
+      socket.writeAll(
+        [
+          'HTTP/1.1 200 OK',
+          'Access-Control-Allow-Origin: *',
+          'Content-Length: 0',
+          '\r\n', // Add \r\n at the end of this header section.
+        ],
+        '\r\n', // Separate each field by \r\n.
+      );
+      await socket.close();
+    });
+
+  channel.sink.add(server.port);
+  await channel
+      .stream.first; // Any writes indicates that the server should exit.
+  unawaited(server.close());
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_vm.dart
new file mode 100644
index 0000000..1f30e5f
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_vm.dart
@@ -0,0 +1,14 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+
+import 'request_cookies_server.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server in the same process.
+Future<StreamChannel<Object?>> startServer() async {
+  final controller = StreamChannelController<Object?>(sync: true);
+  hybridMain(controller.foreign);
+  return controller.local;
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_web.dart
new file mode 100644
index 0000000..31d961b
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_cookies_server_web.dart
@@ -0,0 +1,11 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server out-of-process.
+Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
+    scheme: 'package',
+    path: 'http_client_conformance_tests/src/request_cookies_server.dart'));
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_cookies_test.dart b/pkgs/http_client_conformance_tests/lib/src/request_cookies_test.dart
new file mode 100644
index 0000000..a4eb78c
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_cookies_test.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+import 'package:http/http.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+import 'request_cookies_server_vm.dart'
+    if (dart.library.js_interop) 'request_cookies_server_web.dart';
+
+// The an HTTP header into [name, value].
+final headerSplitter = RegExp(':[ \t]+');
+
+/// Tests that the [Client] correctly sends "cookie" headers in the request.
+///
+/// If [canSendCookieHeaders] is `false` then tests that require that "cookie"
+/// headers be sent by the client will not be run.
+void testRequestCookies(Client client,
+    {bool canSendCookieHeaders = false}) async {
+  group('request cookies', () {
+    late final String host;
+    late final StreamChannel<Object?> httpServerChannel;
+    late final StreamQueue<Object?> httpServerQueue;
+
+    setUpAll(() async {
+      httpServerChannel = await startServer();
+      httpServerQueue = StreamQueue(httpServerChannel.stream);
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
+    });
+    tearDownAll(() => httpServerChannel.sink.add(null));
+
+    test('one cookie', () async {
+      await client
+          .get(Uri.http(host, ''), headers: {'cookie': 'SID=298zf09hf012fh2'});
+
+      final cookies = (await httpServerQueue.next as List).cast<String>();
+      expect(cookies, hasLength(1));
+      final [header, value] = cookies[0].split(headerSplitter);
+      expect(header.toLowerCase(), 'cookie');
+      expect(value, 'SID=298zf09hf012fh2');
+    }, skip: canSendCookieHeaders ? false : 'cannot send cookie headers');
+
+    test('multiple cookies semicolon separated', () async {
+      await client.get(Uri.http(host, ''),
+          headers: {'cookie': 'SID=298zf09hf012fh2; lang=en-US'});
+
+      final cookies = (await httpServerQueue.next as List).cast<String>();
+      expect(cookies, hasLength(1));
+      final [header, value] = cookies[0].split(headerSplitter);
+      expect(header.toLowerCase(), 'cookie');
+      expect(value, 'SID=298zf09hf012fh2; lang=en-US');
+    }, skip: canSendCookieHeaders ? false : 'cannot send cookie headers');
+  });
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_headers_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/request_headers_server_vm.dart
index 44e6565..dc930dc 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_headers_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_headers_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'request_headers_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_headers_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/request_headers_server_web.dart
index 62e8d9e..a15b69b 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_headers_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_headers_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_headers_tests.dart b/pkgs/http_client_conformance_tests/lib/src/request_headers_tests.dart
index 8adf98c..24d94d8 100644
--- a/pkgs/http_client_conformance_tests/lib/src/request_headers_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/request_headers_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'request_headers_server_vm.dart'
-    if (dart.library.html) 'request_headers_server_web.dart';
+    if (dart.library.js_interop) 'request_headers_server_web.dart';
 
 /// Tests that the [Client] correctly sends headers in the request.
 void testRequestHeaders(Client client) async {
@@ -20,7 +20,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_methods_server.dart b/pkgs/http_client_conformance_tests/lib/src/request_methods_server.dart
new file mode 100644
index 0000000..bf05ec0
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_methods_server.dart
@@ -0,0 +1,41 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:stream_channel/stream_channel.dart';
+
+/// Starts an HTTP server that captures the request headers.
+///
+/// Channel protocol:
+///    On Startup:
+///     - send port
+///    On Request Received:
+///     - send the received request method (e.g. GET) as a String
+///    When Receive Anything:
+///     - exit
+void hybridMain(StreamChannel<Object?> channel) async {
+  late HttpServer server;
+
+  server = (await HttpServer.bind('localhost', 0))
+    ..listen((request) async {
+      request.response.headers.set('Access-Control-Allow-Origin', '*');
+      if (request.method == 'OPTIONS') {
+        // Handle a CORS preflight request:
+        // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests
+        request.response.headers
+          ..set('Access-Control-Allow-Methods', '*')
+          ..set('Access-Control-Allow-Headers', '*');
+      } else {
+        channel.sink.add(request.method);
+      }
+      unawaited(request.response.close());
+    });
+
+  channel.sink.add(server.port);
+  await channel
+      .stream.first; // Any writes indicates that the server should exit.
+  unawaited(server.close());
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_methods_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/request_methods_server_vm.dart
new file mode 100644
index 0000000..fa25735
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_methods_server_vm.dart
@@ -0,0 +1,14 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+
+import 'request_methods_server.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server in the same process.
+Future<StreamChannel<Object?>> startServer() async {
+  final controller = StreamChannelController<Object?>(sync: true);
+  hybridMain(controller.foreign);
+  return controller.local;
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_methods_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/request_methods_server_web.dart
new file mode 100644
index 0000000..f9c924e
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_methods_server_web.dart
@@ -0,0 +1,11 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server out-of-process.
+Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
+    scheme: 'package',
+    path: 'http_client_conformance_tests/src/request_methods_server.dart'));
diff --git a/pkgs/http_client_conformance_tests/lib/src/request_methods_tests.dart b/pkgs/http_client_conformance_tests/lib/src/request_methods_tests.dart
new file mode 100644
index 0000000..802f57e
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/request_methods_tests.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+import 'package:http/http.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+import 'request_methods_server_vm.dart'
+    if (dart.library.js_interop) 'request_methods_server_web.dart';
+
+/// Tests that the [Client] correctly sends HTTP request methods
+/// (e.g. GET, HEAD).
+///
+/// If [preservesMethodCase] is `false` then tests that assume that the
+/// [Client] preserves custom request method casing will be skipped.
+void testRequestMethods(Client client,
+    {bool preservesMethodCase = true}) async {
+  group('request methods', () {
+    late final String host;
+    late final StreamChannel<Object?> httpServerChannel;
+    late final StreamQueue<Object?> httpServerQueue;
+
+    setUpAll(() async {
+      httpServerChannel = await startServer();
+      httpServerQueue = StreamQueue(httpServerChannel.stream);
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
+    });
+    tearDownAll(() => httpServerChannel.sink.add(null));
+
+    test('custom method - not case preserving', () async {
+      await client.send(Request(
+        'CuStOm',
+        Uri.http(host, ''),
+      ));
+      final method = await httpServerQueue.next as String;
+      expect('CUSTOM', method.toUpperCase());
+    });
+
+    test('custom method case preserving', () async {
+      await client.send(Request(
+        'CuStOm',
+        Uri.http(host, ''),
+      ));
+      final method = await httpServerQueue.next as String;
+      expect('CuStOm', method);
+    },
+        skip: preservesMethodCase
+            ? false
+            : 'does not preserve HTTP request method case');
+
+    test('delete', () async {
+      await client.delete(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('DELETE', method);
+    });
+
+    test('get', () async {
+      await client.get(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('GET', method);
+    });
+    test('head', () async {
+      await client.head(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('HEAD', method);
+    });
+
+    test('patch', () async {
+      await client.patch(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('PATCH', method);
+    });
+
+    test('post', () async {
+      await client.post(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('POST', method);
+    });
+
+    test('put', () async {
+      await client.put(Uri.http(host, ''));
+      final method = await httpServerQueue.next as String;
+      expect('PUT', method);
+    });
+  });
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_server_vm.dart
index f88e065..a12b6fb 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'response_body_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_server_web.dart
index 94bdaa9..4d23a48 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_vm.dart
index 01d84a1..4e4eaff 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'response_body_streamed_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_web.dart
index a9ce00b..e04ebd6 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_test.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_test.dart
index 28686fa..f355d6c 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_test.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_streamed_test.dart
@@ -10,7 +10,7 @@
 import 'package:test/test.dart';
 
 import 'response_body_streamed_server_vm.dart'
-    if (dart.library.html) 'response_body_streamed_server_web.dart';
+    if (dart.library.js_interop) 'response_body_streamed_server_web.dart';
 
 /// Tests that the [Client] correctly implements HTTP responses with bodies of
 /// unbounded size.
@@ -28,7 +28,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_body_tests.dart b/pkgs/http_client_conformance_tests/lib/src/response_body_tests.dart
index 91ee549..34c29f6 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_body_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_body_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'response_body_server_vm.dart'
-    if (dart.library.html) 'response_body_server_web.dart';
+    if (dart.library.js_interop) 'response_body_server_web.dart';
 
 /// Tests that the [Client] correctly implements HTTP responses with bodies.
 ///
@@ -26,7 +26,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_cookies_server.dart b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server.dart
new file mode 100644
index 0000000..392e228
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server.dart
@@ -0,0 +1,44 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:async/async.dart';
+import 'package:stream_channel/stream_channel.dart';
+
+/// Starts an HTTP server that returns a custom status line.
+///
+/// Channel protocol:
+///    On Startup:
+///     - send port
+///    On Request Received:
+///     - load response status line from channel
+///     - exit
+void hybridMain(StreamChannel<Object?> channel) async {
+  late HttpServer server;
+  final clientQueue = StreamQueue(channel.stream);
+
+  server = (await HttpServer.bind('localhost', 0))
+    ..listen((request) async {
+      await request.drain<void>();
+      final socket = await request.response.detachSocket(writeHeaders: false);
+
+      final headers = (await clientQueue.next) as List;
+      socket.writeAll(
+        [
+          'HTTP/1.1 200 OK',
+          'Access-Control-Allow-Origin: *',
+          'Content-Length: 0',
+          ...headers,
+          '\r\n', // Add \r\n at the end of this header section.
+        ],
+        '\r\n', // Separate each field by \r\n.
+      );
+      await socket.close();
+      unawaited(server.close());
+    });
+
+  channel.sink.add(server.port);
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_vm.dart
new file mode 100644
index 0000000..2edbb45
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_vm.dart
@@ -0,0 +1,14 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+
+import 'response_cookies_server.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server in the same process.
+Future<StreamChannel<Object?>> startServer() async {
+  final controller = StreamChannelController<Object?>(sync: true);
+  hybridMain(controller.foreign);
+  return controller.local;
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_web.dart
new file mode 100644
index 0000000..cb8e384
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/response_cookies_server_web.dart
@@ -0,0 +1,11 @@
+// Generated by generate_server_wrappers.dart. Do not edit.
+
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
+/// Starts the redirect test HTTP server out-of-process.
+Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
+    scheme: 'package',
+    path: 'http_client_conformance_tests/src/response_cookies_server.dart'));
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_cookies_test.dart b/pkgs/http_client_conformance_tests/lib/src/response_cookies_test.dart
new file mode 100644
index 0000000..f8e154d
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/response_cookies_test.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+import 'package:http/http.dart';
+import 'package:stream_channel/stream_channel.dart';
+import 'package:test/test.dart';
+
+import 'response_cookies_server_vm.dart'
+    if (dart.library.js_interop) 'response_cookies_server_web.dart';
+
+/// Tests that the [Client] correctly receives "set-cookie-headers"
+///
+/// If [canReceiveSetCookieHeaders] is `false` then tests that require that
+/// "set-cookie" headers be received by the client will not be run.
+void testResponseCookies(Client client,
+    {required bool canReceiveSetCookieHeaders}) async {
+  group('response cookies', () {
+    late String host;
+    late StreamChannel<Object?> httpServerChannel;
+    late StreamQueue<Object?> httpServerQueue;
+
+    setUp(() async {
+      httpServerChannel = await startServer();
+      httpServerQueue = StreamQueue(httpServerChannel.stream);
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
+    });
+
+    test('single session cookie', () async {
+      httpServerChannel.sink.add(['Set-Cookie: SID=1231AB3']);
+      final response = await client.get(Uri.http(host, ''));
+
+      expect(response.headers['set-cookie'], 'SID=1231AB3');
+    },
+        skip: canReceiveSetCookieHeaders
+            ? false
+            : 'cannot receive set-cookie headers');
+
+    test('multiple session cookies', () async {
+      // RFC-2616 4.2 says:
+      // "The field value MAY be preceded by any amount of LWS, though a single
+      // SP is preferred." and
+      // "The field-content does not include any leading or trailing LWS ..."
+      httpServerChannel.sink
+          .add(['Set-Cookie: SID=1231AB3', 'Set-Cookie: lang=en_US']);
+      final response = await client.get(Uri.http(host, ''));
+
+      expect(
+          response.headers['set-cookie'],
+          matches(r'SID=1231AB3'
+              r'[ \t]*,[ \t]*'
+              r'lang=en_US'));
+    },
+        skip: canReceiveSetCookieHeaders
+            ? false
+            : 'cannot receive set-cookie headers');
+
+    test('permanent cookie with expires', () async {
+      httpServerChannel.sink
+          .add(['Set-Cookie: id=a3fWa; Expires=Wed, 10 Jan 2024 07:28:00 GMT']);
+      final response = await client.get(Uri.http(host, ''));
+
+      expect(response.headers['set-cookie'],
+          'id=a3fWa; Expires=Wed, 10 Jan 2024 07:28:00 GMT');
+    },
+        skip: canReceiveSetCookieHeaders
+            ? false
+            : 'cannot receive set-cookie headers');
+
+    test('multiple permanent cookies with expires', () async {
+      // RFC-2616 4.2 says:
+      // "The field value MAY be preceded by any amount of LWS, though a single
+      // SP is preferred." and
+      // "The field-content does not include any leading or trailing LWS ..."
+      httpServerChannel.sink.add([
+        'Set-Cookie: id=a3fWa; Expires=Wed, 10 Jan 2024 07:28:00 GMT',
+        'Set-Cookie: id=2fasd; Expires=Wed, 21 Oct 2025 07:28:00 GMT'
+      ]);
+      final response = await client.get(Uri.http(host, ''));
+
+      expect(
+          response.headers['set-cookie'],
+          matches(r'id=a3fWa; Expires=Wed, 10 Jan 2024 07:28:00 GMT'
+              r'[ \t]*,[ \t]*'
+              r'id=2fasd; Expires=Wed, 21 Oct 2025 07:28:00 GMT'));
+    },
+        skip: canReceiveSetCookieHeaders
+            ? false
+            : 'cannot receive set-cookie headers');
+  });
+}
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_headers_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/response_headers_server_vm.dart
index b7d4a01..c99a021 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_headers_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_headers_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'response_headers_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_headers_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/response_headers_server_web.dart
index 8ee938a..0e6dabd 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_headers_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_headers_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_headers_tests.dart b/pkgs/http_client_conformance_tests/lib/src/response_headers_tests.dart
index 4f92042..84f0fb6 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_headers_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_headers_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'response_headers_server_vm.dart'
-    if (dart.library.html) 'response_headers_server_web.dart';
+    if (dart.library.js_interop) 'response_headers_server_web.dart';
 
 /// Tests that the [Client] correctly processes response headers.
 void testResponseHeaders(Client client) async {
@@ -20,7 +20,7 @@
     setUp(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
 
     test('single header', () async {
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_vm.dart
index ff2ea84..053bd11 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'response_status_line_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_web.dart
index f1ebbcb..d70a325 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_status_line_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/response_status_line_tests.dart b/pkgs/http_client_conformance_tests/lib/src/response_status_line_tests.dart
index b618fa0..6eb70c5 100644
--- a/pkgs/http_client_conformance_tests/lib/src/response_status_line_tests.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/response_status_line_tests.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'response_status_line_server_vm.dart'
-    if (dart.library.html) 'response_status_line_server_web.dart';
+    if (dart.library.js_interop) 'response_status_line_server_web.dart';
 
 /// Tests that the [Client] correctly processes the response status line (e.g.
 /// 'HTTP/1.1 200 OK\r\n').
@@ -23,7 +23,7 @@
     setUp(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
 
     test('complete', () async {
diff --git a/pkgs/http_client_conformance_tests/lib/src/server_errors_server_vm.dart b/pkgs/http_client_conformance_tests/lib/src/server_errors_server_vm.dart
index 257adcf..e5aa09f 100644
--- a/pkgs/http_client_conformance_tests/lib/src/server_errors_server_vm.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/server_errors_server_vm.dart
@@ -4,6 +4,8 @@
 
 import 'server_errors_server.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server in the same process.
 Future<StreamChannel<Object?>> startServer() async {
   final controller = StreamChannelController<Object?>(sync: true);
diff --git a/pkgs/http_client_conformance_tests/lib/src/server_errors_server_web.dart b/pkgs/http_client_conformance_tests/lib/src/server_errors_server_web.dart
index cc763e3..9614f36 100644
--- a/pkgs/http_client_conformance_tests/lib/src/server_errors_server_web.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/server_errors_server_web.dart
@@ -3,6 +3,8 @@
 import 'package:stream_channel/stream_channel.dart';
 import 'package:test/test.dart';
 
+export 'server_queue_helpers.dart' show StreamQueueOfNullableObjectExtension;
+
 /// Starts the redirect test HTTP server out-of-process.
 Future<StreamChannel<Object?>> startServer() async => spawnHybridUri(Uri(
     scheme: 'package',
diff --git a/pkgs/http_client_conformance_tests/lib/src/server_errors_test.dart b/pkgs/http_client_conformance_tests/lib/src/server_errors_test.dart
index 65de499..1a83696 100644
--- a/pkgs/http_client_conformance_tests/lib/src/server_errors_test.dart
+++ b/pkgs/http_client_conformance_tests/lib/src/server_errors_test.dart
@@ -8,7 +8,7 @@
 import 'package:test/test.dart';
 
 import 'server_errors_server_vm.dart'
-    if (dart.library.html) 'server_errors_server_web.dart';
+    if (dart.library.js_interop) 'server_errors_server_web.dart';
 
 /// Tests that the [Client] correctly handles server errors.
 void testServerErrors(Client client, {bool redirectAlwaysAllowed = false}) {
@@ -20,7 +20,7 @@
     setUpAll(() async {
       httpServerChannel = await startServer();
       httpServerQueue = StreamQueue(httpServerChannel.stream);
-      host = 'localhost:${await httpServerQueue.next}';
+      host = 'localhost:${await httpServerQueue.nextAsInt}';
     });
     tearDownAll(() => httpServerChannel.sink.add(null));
 
diff --git a/pkgs/http_client_conformance_tests/lib/src/server_queue_helpers.dart b/pkgs/http_client_conformance_tests/lib/src/server_queue_helpers.dart
new file mode 100644
index 0000000..df87ddd
--- /dev/null
+++ b/pkgs/http_client_conformance_tests/lib/src/server_queue_helpers.dart
@@ -0,0 +1,10 @@
+// Copyright (c) 2023, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:async/async.dart';
+
+extension StreamQueueOfNullableObjectExtension on StreamQueue<Object?> {
+  /// When run under dart2wasm, JSON numbers are always returned as [double].
+  Future<int> get nextAsInt async => ((await next) as num).toInt();
+}
diff --git a/pkgs/http_client_conformance_tests/pubspec.yaml b/pkgs/http_client_conformance_tests/pubspec.yaml
index 4264059..f904b2d 100644
--- a/pkgs/http_client_conformance_tests/pubspec.yaml
+++ b/pkgs/http_client_conformance_tests/pubspec.yaml
@@ -2,18 +2,19 @@
 description: >-
   A library that tests whether implementations of package:http's `Client` class
   behave as expected.
-publish_to: none
 repository: https://github.com/dart-lang/http/tree/master/pkgs/http_client_conformance_tests
 
+publish_to: none
+
 environment:
-  sdk: '>=2.19.0 <3.0.0'
+  sdk: ^3.2.0
 
 dependencies:
   async: ^2.8.2
   dart_style: ^2.2.3
-  http: '>=0.13.4 <2.0.0'
+  http: ^1.2.0
   stream_channel: ^2.1.1
   test: ^1.21.2
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
diff --git a/pkgs/http_profile/pubspec.yaml b/pkgs/http_profile/pubspec.yaml
index 9e3b69e..1b5891e 100644
--- a/pkgs/http_profile/pubspec.yaml
+++ b/pkgs/http_profile/pubspec.yaml
@@ -1,10 +1,11 @@
 name: http_profile
 description: >-
-  A library used by HTTP client authors to integrate with the DevTools
-  Network tab.
-publish_to: none
+  A library used by HTTP client authors to integrate with the DevTools Network
+  tab.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/http_profile
 
+publish_to: none
+
 environment:
   sdk: ^3.0.0
 
diff --git a/pkgs/java_http/pubspec.yaml b/pkgs/java_http/pubspec.yaml
index ecba4f1..c04429a 100644
--- a/pkgs/java_http/pubspec.yaml
+++ b/pkgs/java_http/pubspec.yaml
@@ -1,7 +1,9 @@
 name: java_http
-description: A Dart package for making HTTP requests using java.net.HttpURLConnection.
 version: 0.0.1
+description: >-
+  A Dart package for making HTTP requests using java.net.HttpURLConnection.
 repository: https://github.com/dart-lang/http/tree/master/pkgs/java_http
+
 publish_to: none
 
 environment:
@@ -14,9 +16,8 @@
   path: ^1.8.0
 
 dev_dependencies:
-  dart_flutter_team_lints: ^1.0.0
+  dart_flutter_team_lints: ^2.0.0
   http_client_conformance_tests:
     path: ../http_client_conformance_tests/
   jnigen: ^0.5.0
-  lints: ^2.0.0
   test: ^1.21.0
diff --git a/tool/ci.sh b/tool/ci.sh
index 26395d2..d4cc8d2 100755
--- a/tool/ci.sh
+++ b/tool/ci.sh
@@ -1,9 +1,10 @@
 #!/bin/bash
-# Created with package:mono_repo v6.6.0
+# Created with package:mono_repo v6.6.1
 
 # Support built in commands on windows out of the box.
+
 # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter")
-# then "flutter" is called instead of "pub".
+# then "flutter pub" is called instead of "dart pub".
 # This assumes that the Flutter SDK has been installed in a previous step.
 function pub() {
   if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then
@@ -12,18 +13,13 @@
     command dart pub "$@"
   fi
 }
-# When it is a flutter repo (check the pubspec.yaml for "sdk: flutter")
-# then "flutter" is called instead of "pub".
-# This assumes that the Flutter SDK has been installed in a previous step.
+
 function format() {
-  if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then
-    command flutter format "$@"
-  else
-    command dart format "$@"
-  fi
+  command dart format "$@"
 }
+
 # When it is a flutter repo (check the pubspec.yaml for "sdk: flutter")
-# then "flutter" is called instead of "pub".
+# then "flutter analyze" is called instead of "dart analyze".
 # This assumes that the Flutter SDK has been installed in a previous step.
 function analyze() {
   if grep -Fq "sdk: flutter" "${PWD}/pubspec.yaml"; then
@@ -99,6 +95,10 @@
         echo 'dart test --platform chrome'
         dart test --platform chrome || EXIT_CODE=$?
         ;;
+      test_4)
+        echo 'dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm'
+        dart test --test-randomize-ordering-seed=random -p chrome -c dart2wasm || EXIT_CODE=$?
+        ;;
       *)
         echo -e "\033[31mUnknown TASK '${TASK}' - TERMINATING JOB\033[0m"
         exit 64