mojo_client.dart 8.86 KB
Newer Older
1 2 3 4 5 6 7 8
// Copyright (c) 2012, 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:typed_data';

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/services.dart';
11
import 'package:mojo/core.dart' as mojo;
12
import 'package:mojo/mojo/http_header.mojom.dart' as mojom;
13 14
import 'package:mojo/mojo/url_request.mojom.dart' as mojom;
import 'package:mojo/mojo/url_response.mojom.dart' as mojom;
15 16
import 'package:mojo_services/mojo/network_service.mojom.dart' as mojom;
import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojom;
17 18 19

import 'response.dart';

Florian Loitsch's avatar
Florian Loitsch committed
20
/// A `mojo`-based HTTP client.
21 22
class MojoClient {

23 24 25 26 27
  /// Sends an HTTP HEAD request with the given headers to the given URL, which
  /// can be a [Uri] or a [String].
  Future<Response> head(dynamic url, { Map<String, String> headers }) {
    return _send("HEAD", url, headers);
  }
28

29 30 31 32 33
  /// Sends an HTTP GET request with the given headers to the given URL, which can
  /// be a [Uri] or a [String].
  Future<Response> get(dynamic url, { Map<String, String> headers }) {
    return _send("GET", url, headers);
  }
34

35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
  /// Sends an HTTP POST request with the given headers and body to the given URL,
  /// which can be a [Uri] or a [String].
  ///
  /// [body] sets the body of the request. It can be a [String], a [List<int>] or
  /// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
  /// used as the body of the request. The content-type of the request will
  /// default to "text/plain".
  ///
  /// If [body] is a List, it's used as a list of bytes for the body of the
  /// request.
  ///
  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
  /// content-type of the request will be set to
  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
  ///
  /// [encoding] defaults to [UTF8].
  Future<Response> post(dynamic url, { Map<String, String> headers, dynamic body, Encoding encoding: UTF8 }) {
    return _send("POST", url, headers, body, encoding);
  }
54

55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
  /// Sends an HTTP PUT request with the given headers and body to the given URL,
  /// which can be a [Uri] or a [String].
  ///
  /// [body] sets the body of the request. It can be a [String], a [List<int>] or
  /// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
  /// used as the body of the request. The content-type of the request will
  /// default to "text/plain".
  ///
  /// If [body] is a List, it's used as a list of bytes for the body of the
  /// request.
  ///
  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
  /// content-type of the request will be set to
  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
  ///
  /// [encoding] defaults to [UTF8].
  Future<Response> put(dynamic url, { Map<String, String> headers, dynamic body, Encoding encoding: UTF8 }) {
    return _send("PUT", url, headers, body, encoding);
  }
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
  /// Sends an HTTP PATCH request with the given headers and body to the given
  /// URL, which can be a [Uri] or a [String].
  ///
  /// [body] sets the body of the request. It can be a [String], a [List<int>] or
  /// a [Map<String, String>]. If it's a String, it's encoded using [encoding] and
  /// used as the body of the request. The content-type of the request will
  /// default to "text/plain".
  ///
  /// If [body] is a List, it's used as a list of bytes for the body of the
  /// request.
  ///
  /// If [body] is a Map, it's encoded as form fields using [encoding]. The
  /// content-type of the request will be set to
  /// `"application/x-www-form-urlencoded"`; this cannot be overridden.
  ///
  /// [encoding] defaults to [UTF8].
  Future<Response> patch(dynamic url, {Map<String, String> headers, dynamic body, Encoding encoding: UTF8 }) {
    return _send("PATCH", url, headers, body, encoding);
  }
94

95 96 97 98 99
  /// Sends an HTTP DELETE request with the given headers to the given URL, which
  /// can be a [Uri] or a [String].
  Future<Response> delete(dynamic url, { Map<String, String> headers }) {
    return _send("DELETE", url, headers);
  }
100

101 102 103 104 105 106 107 108
  /// Sends an HTTP GET request with the given headers to the given URL, which can
  /// be a [Uri] or a [String], and returns a Future that completes to the body of
  /// the response as a [String].
  ///
  /// The Future will emit a [ClientException] if the response doesn't have a
  /// success status code.
  Future<String> read(dynamic url, { Map<String, String> headers }) {
    return get(url, headers: headers).then((Response response) {
109 110 111 112 113
      _checkResponseSuccess(url, response);
      return response.body;
    });
  }

114 115 116 117 118 119 120
  /// Sends an HTTP GET request with the given headers to the given URL, which can
  /// be a [Uri] or a [String], and returns a Future that completes to the body of
  /// the response as a list of bytes.
  ///
  /// The Future will emit a [ClientException] if the response doesn't have a
  /// success status code.
  Future<Uint8List> readBytes(dynamic url, { Map<String, String> headers }) {
121
    return get(url, headers: headers).then((Response response) {
122 123 124 125 126
      _checkResponseSuccess(url, response);
      return response.bodyBytes;
    });
  }

Adam Barth's avatar
Adam Barth committed
127
  Future<mojo.MojoDataPipeConsumer> readDataPipe(dynamic url, { Map<String, String> headers }) async {
128
    mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
Adam Barth's avatar
Adam Barth committed
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    mojom.UrlRequest request = _prepareRequest('get', url, headers);
    mojom.UrlResponse response;
    try {
      networkService.ptr.createUrlLoader(loader);
      response = (await loader.ptr.start(request)).response;
    } catch (exception, stack) {
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'networking HTTP library',
        context: 'while sending bytes to the Mojo network library',
        silent: true
      ));
      return null;
    } finally {
      loader.close();
    }
    if (response.statusCode < 400)
      return response.body;
    throw new Exception("Request to $url failed with status ${response.statusCode}.");
  }

  mojom.UrlRequest _prepareRequest(String method, dynamic url, Map<String, String> headers, [dynamic body, Encoding encoding = UTF8]) {
152
    List<mojom.HttpHeader> mojoHeaders = <mojom.HttpHeader>[];
153
    headers?.forEach((String name, String value) {
154
      mojom.HttpHeader header = new mojom.HttpHeader()
155 156 157 158
        ..name = name
        ..value = value;
      mojoHeaders.add(header);
    });
159
    mojom.UrlRequest request = new mojom.UrlRequest()
160
      ..url = url.toString()
161
      ..headers = mojoHeaders
162 163 164 165
      ..method = method;
    if (body != null) {
      mojo.MojoDataPipe pipe = new mojo.MojoDataPipe();
      request.body = <mojo.MojoDataPipeConsumer>[pipe.consumer];
166
      Uint8List encodedBody = encoding.encode(body);
167
      ByteData data = new ByteData.view(encodedBody.buffer);
168 169
      mojo.DataPipeFiller.fillHandle(pipe.producer, data);
    }
Adam Barth's avatar
Adam Barth committed
170 171 172 173 174 175
    return request;
  }

  Future<Response> _send(String method, dynamic url, Map<String, String> headers, [dynamic body, Encoding encoding = UTF8]) async {
    mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
    mojom.UrlRequest request = _prepareRequest(method, url, headers, body, encoding);
176
    try {
177
      networkService.ptr.createUrlLoader(loader);
178
      mojom.UrlResponse response = (await loader.ptr.start(request)).response;
179
      ByteData data = await mojo.DataPipeDrainer.drainHandle(response.body);
180
      Uint8List bodyBytes = new Uint8List.view(data.buffer);
181 182 183 184 185 186 187
      Map<String, String> headers = <String, String>{};
      for (mojom.HttpHeader header in response.headers) {
        String headerName = header.name.toLowerCase();
        String existingValue = headers[headerName];
        headers[headerName] = existingValue != null ? '$existingValue, ${header.value}' : header.value;
      }
      return new Response.bytes(bodyBytes, response.statusCode, headers: headers);
188 189 190 191 192 193 194 195
    } catch (exception, stack) {
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
        stack: stack,
        library: 'networking HTTP library',
        context: 'while sending bytes to the Mojo network library',
        silent: true
      ));
196
      return new Response.bytes(null, 500);
197 198 199 200 201
    } finally {
      loader.close();
    }
  }

202
  void _checkResponseSuccess(dynamic url, Response response) {
203 204 205
    if (response.statusCode < 400)
      return;
    throw new Exception("Request to $url failed with status ${response.statusCode}.");
206 207
  }

208 209
  static mojom.NetworkServiceProxy _initNetworkService() {
    mojom.NetworkServiceProxy proxy = new mojom.NetworkServiceProxy.unbound();
210 211 212 213
    shell.connectToService("mojo:authenticated_network_service", proxy);
    return proxy;
  }

214
  /// A handle to the [NetworkService] object used by [MojoClient].
215
  static final mojom.NetworkServiceProxy networkService = _initNetworkService();
216
}