-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cloud_http): Add utils package (#37)
Adds a package with helpers for running Dart server apps in a cloud environments. Includes tracing and logging helpers.
- Loading branch information
Showing
15 changed files
with
1,268 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
name: cloud_http | ||
on: | ||
pull_request: | ||
paths: | ||
- ".github/workflows/cloud_http.yaml" | ||
- "packages/cloud_http/**" | ||
|
||
# Prevent duplicate runs due to Graphite | ||
# https://graphite.dev/docs/troubleshooting#why-are-my-actions-running-twice | ||
concurrency: | ||
group: ${{ github.repository }}-${{ github.workflow }}-${{ github.ref }}-${{ github.ref == 'refs/heads/main' && github.sha || ''}} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
check: | ||
strategy: | ||
fail-fast: true | ||
matrix: | ||
sdk: | ||
- stable | ||
- "3.3" | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 5 | ||
steps: | ||
- name: Git Checkout | ||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # 4.1.7 | ||
with: | ||
submodules: recursive | ||
- name: Setup Dart | ||
uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 # 1.6.5 | ||
with: | ||
sdk: ${{ matrix.sdk }} | ||
- name: Create override | ||
working-directory: packages/cloud_http | ||
run: | | ||
cat <<EOF > pubspec_overrides.yaml | ||
dependency_overrides: | ||
http_sfv: | ||
path: ../http_sfv | ||
EOF | ||
- name: Get Packages | ||
working-directory: packages/cloud_http | ||
run: dart pub get | ||
- name: Analyze | ||
working-directory: packages/cloud_http | ||
run: dart analyze | ||
- name: Format | ||
working-directory: packages/cloud_http | ||
run: dart format --set-exit-if-changed . | ||
- name: Test | ||
working-directory: packages/cloud_http | ||
run: dart test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
.DS_Store | ||
.DS_Store | ||
pubspec_overrides.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# https://dart.dev/guides/libraries/private-files | ||
# Created by `dart pub` | ||
.dart_tool/ | ||
|
||
# Avoid committing pubspec.lock for library packages; see | ||
# https://dart.dev/guides/libraries/private-files#pubspeclock. | ||
pubspec.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 0.1.0 | ||
|
||
- Initial version. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# cloud_http | ||
|
||
Utilities for running Dart server applications in cloud environments. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include: package:lints/recommended.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
library; | ||
|
||
export 'src/tracing/trace_context.dart'; | ||
export 'src/tracing/trace_parent.dart'; | ||
export 'src/tracing/trace_state.dart'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import 'dart:math'; | ||
|
||
import 'package:cloud_http/cloud_http.dart'; | ||
import 'package:convert/convert.dart'; | ||
import 'package:shelf/shelf.dart' as shelf; | ||
|
||
/// A trace context [shelf.Middleware] which conforms to the W3C Trace Context | ||
/// specification. | ||
/// | ||
/// See: https://w3c.github.io/trace-context/ | ||
shelf.Middleware tracingMiddleware({ | ||
Random? random, | ||
}) { | ||
random ??= Random.secure(); | ||
return (shelf.Handler innerHandler) { | ||
return (shelf.Request request) { | ||
var traceContext = TraceContext.fromHeaders(request.headers); | ||
var traceparent = traceContext.traceparent; | ||
|
||
if (traceparent == null) { | ||
// A vendor receiving a request without a traceparent header SHOULD | ||
// generate traceparent headers for outbound requests, effectively | ||
// starting a new trace. | ||
traceparent = Traceparent.create( | ||
traceId: hex.encode( | ||
List<int>.generate(16, (_) => random!.nextInt(256)), | ||
), | ||
parentId: hex.encode( | ||
List<int>.generate(8, (_) => random!.nextInt(256)), | ||
), | ||
sampled: true, | ||
random: true, | ||
); | ||
} else { | ||
// https://www.w3.org/TR/trace-context-2/#a-traceparent-is-received | ||
// | ||
// The vendor MUST modify the traceparent header: | ||
// - Update parent-id: The value of property parent-id MUST be set to a | ||
// value representing the ID of the current operation. | ||
// - Update sampled: The value of sampled reflects the caller's | ||
// recording behavior. The value of the sampled flag of trace-flags | ||
// MAY be set to 1 if the trace data is likely to be recorded or to 0 | ||
// otherwise. Setting the flag is no guarantee that the trace will be | ||
// recorded but increases the likeliness of end-to-end recorded traces. | ||
traceparent = traceparent.copyWith( | ||
parentId: hex.encode( | ||
List<int>.generate(8, (_) => random!.nextInt(256)), | ||
), | ||
sampled: true, | ||
); | ||
} | ||
|
||
traceContext = TraceContext( | ||
traceparent: traceparent, | ||
tracestate: traceContext.tracestate, | ||
); | ||
|
||
return innerHandler( | ||
request.change(headers: { | ||
...request.headers, | ||
...traceContext.toHeaders(), | ||
}), | ||
); | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import 'package:cloud_http/src/tracing/trace_parent.dart'; | ||
import 'package:cloud_http/src/tracing/trace_state.dart'; | ||
|
||
/// The trace context for a request, as defined in the W3C Trace Context | ||
/// [specification](https://www.w3.org/TR/trace-context-2). | ||
final class TraceContext { | ||
const TraceContext({ | ||
required Traceparent this.traceparent, | ||
this.tracestate, | ||
}); | ||
|
||
const TraceContext._({ | ||
this.traceparent, | ||
this.tracestate, | ||
}); | ||
|
||
/// Parses the `traceparent` and `tracestate` headers from a request's | ||
/// [headers]. | ||
/// | ||
/// If the `traceparent` header is missing or invalid, the returned context | ||
/// will have a | ||
/// | ||
/// If the `tracestate` header is missing or invalid, the returned context | ||
/// will have a `null` [tracestate]. | ||
/// | ||
/// Assumes that [headers] is a case-insensitive [Map]. | ||
factory TraceContext.fromHeaders(Map<String, Object>? headers) { | ||
final traceparentHeader = switch (headers?['traceparent']) { | ||
null => null, | ||
final String traceparent => traceparent, | ||
final List<String> traceparent => traceparent.singleOrNull, | ||
final invalid => throw FormatException( | ||
'Invalid traceparent header: $invalid. ' | ||
'Expected String or List<String>, got ${invalid.runtimeType}.', | ||
), | ||
}; | ||
final traceparent = traceparentHeader != null | ||
? Traceparent.tryParse(traceparentHeader) | ||
: null; | ||
// If the vendor failed to parse traceparent, it MUST NOT attempt to parse | ||
// tracestate. Note that the opposite is not true: failure to parse | ||
// tracestate MUST NOT affect the parsing of traceparent. | ||
Tracestate? tracestate; | ||
if (traceparent != null) { | ||
final tracestateHeader = switch (headers?['tracestate']) { | ||
null => null, | ||
final String tracestate => tracestate, | ||
// Multiple tracestate header fields MUST be handled as specified by | ||
// RFC9110 Section 5.3 Field Order. | ||
final List<String> tracestate => tracestate.join(', '), | ||
final invalid => throw FormatException( | ||
'Invalid tracestate header: $invalid. ' | ||
'Expected String or List<String>, got ${invalid.runtimeType}.', | ||
), | ||
}; | ||
tracestate = tracestateHeader != null | ||
? Tracestate.tryParse(tracestateHeader) | ||
: null; | ||
} | ||
return TraceContext._( | ||
traceparent: traceparent, | ||
tracestate: tracestate, | ||
); | ||
} | ||
|
||
final Traceparent? traceparent; | ||
final Tracestate? tracestate; | ||
|
||
Map<String, String> toHeaders() => { | ||
// In order to increase interoperability across multiple protocols and | ||
// encourage successful integration, tracing systems SHOULD encode the | ||
// header name as ASCII lowercase. | ||
if (traceparent != null) 'traceparent': traceparent.toString(), | ||
if (tracestate != null) 'tracestate': tracestate.toString(), | ||
}; | ||
} |
Oops, something went wrong.