跳转至

Aqueduct HTTP 片段

Hello, World

class AppChannel extends ApplicationChannel {  
  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/hello_world").linkFunction((request) async {
      return Response.ok("Hello, world!")
        ..contentType = ContentType.TEXT;
    });

    return router;
  }
}

路由变量

class AppChannel extends ApplicationChannel {  
  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/variable/[:variable]").linkFunction((request) async {
      return Response.ok({
        "method": request.raw.method,
        "path": request.path.variables["variable"] ?? "not specified"
      });      
    });

    return router;
  }
}

分组路由和绑定路径变量

class AppChannel extends ApplicationChannel {  
  @override
  Controller get entryPoint {
    final router = Router();

    router
      .route("/users/[:id]")
      .link(() => MyController());

    return router;
  }
}

class MyController extends ResourceController {
  final List<String> things = ['thing1', 'thing2'];

  @Operation.get()
  Future<Response> getThings() async {
    return Response.ok(things);
  }

  @Operation.get('id')
  Future<Response> getThing(@Bind.path('id') int id) async {
    if (id < 0 || id >= things.length) {
      return Response.notFound();
    }
    return Response.ok(things[id]);
  }
}

定制中间件

class AppChannel extends ApplicationChannel {  
  @override
  Controller get entryPoint {
    final router = Router();

    router
      .route("/rate_limit")
      .link(() => RateLimiter())
      .linkFunction((req) async => Response.ok({
        "requests_remaining": req.attachments["remaining"]
      }));

    return router;
  }
}

class RateLimiter extends RequestController {
  @override
  Future<RequestOrResponse> handle(Request request) async {
    final apiKey = request.raw.headers.value("x-apikey");
    final requestsRemaining = await remainingRequestsForAPIKey(apiKey);
    if (requestsRemaining <= 0) {
      return Response(429, null, null);
    }

    request.addResponseModifier((r) {
      r.headers["x-remaining-requests"] = requestsRemaining;
    });

    return request;
  }
}

应用范围的CORS允许的来源

class AppChannel extends ApplicationChannel {
  @override
  Future prepare() async {
    // 默认情况下,所有控制器都将使用此策略
    CORSPolicy.defaultPolicy.allowedOrigins = ["https://mywebsite.com"];
  }

  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/things").linkFunction((request) async {
      return Response.ok(["Widget", "Doodad", "Transformer"]);
    });

    return router;
  }
}

提供文件服务并设置缓存控制标头

class AppChannel extends ApplicationChannel {
  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/files/*").link(() =>
      FileController("web")
        ..addCachePolicy(new CachePolicy(expirationFromNow: new Duration(days: 365)),
          (path) => path.endsWith(".js") || path.endsWith(".css"))      
    );

    return router;
  }
}

流式响应(带有文本/事件流的服务器端事件)

class AppChannel extends ApplicationChannel {
  final StreamController<String> controller = new StreamController<String>();  

  @override
  Future prepare() async {
    var count = 0;
     Timer.periodic(new Duration(seconds: 1), (_) {
      count ++;
      controller.add("This server has been up for $count seconds\n");
    });
  }

  @override
  Controller get entryPoint {
    final router = new Router();

    router.route("/stream").linkFunction((req) async {
      return Response.ok(controller.stream)
          ..bufferOutput = false
          ..contentType = new ContentType(
            "text", "event-stream", charset: "utf-8");
    });

    return router;
  }
}

Websocket服务器

class AppChannel extends ApplicationChannel {
  List<WebSocket> websockets = [];

  @override
  Future prepare() async {
    // 当另一个隔离收到Websocket消息时,将其回显到与此隔离上连接的Websocket
    messageHub.listen(sendBytesToConnectedClients);
  }

  @override
  Controller get entryPoint {
    final router = Router();

    // 允许websocket客户端连接到 ws://host/connect
    router.route("/connect").linkFunction((request) async {
      var websocket = await WebSocketTransformer.upgrade(request.raw);
      websocket.listen(echo, onDone: () {
        websockets.remove(websocket);
      }, cancelOnError: true);
      websockets.add(websocket);

      // 将请求从通道中取出
      return null;
    });

    return router;
  }

  void sendBytesToConnectedClients(List<int> bytes) {
    websockets.forEach((ws) {
      ws.add(bytes);
    });
  }

  void echo(List<int> bytes) {
    sendBytesToConnectedClients(bytes);

    // 发送到其他 isolates
    messageHub.add(bytes);
  }
}

设置内容类型和编码响应体

class AppChannel extends ApplicationChannel {
  final ContentType CSV = ContentType("text", "csv", charset: "utf-8");

  @override
  Future prepare() async {
    // CsvCodec extends dart:convert.Codec
    CodecRegistry.defaultInstance.add(CSV, new CsvCodec());
  }

  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/csv").linkFunction((req) async {
      // 这些值将被CsvCodec转换为一个逗号分隔的字符串
      return Response.ok([[1, 2, 3], ["a", "b", "c"]])
        ..contentType = CSV;
    });

    return router;
  }
}

代理来自另一台服务器的文件

class AppChannel extends ApplicationChannel {
  @override
  Controller get entryPoint {
    final router = Router();

    router.route("/proxy/*").linkFunction((req) async {
      var fileURL = "https://otherserver/${req.path.remainingPath}";
      var fileRequest = await client.getUrl(url);
      var fileResponse = await req.close();
      if (fileResponse.statusCode != 200) {
        return new Response.notFound();
      }

      // A dart:io.HttpResponse is a Stream<List<int>> of its body bytes.
      return new Response.ok(fileResponse)
        ..contentType = fileResponse.headers.contentType
        // 因为数据已经根据内容类型进行了编码,所以才让数据通过; 再次应用编码会导致问题
        ..encodeBody = false;
    });

    return router;
  }
}