Files
proxypin/lib/ui/desktop/left/request_editor.dart

372 lines
11 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:desktop_multi_window/desktop_multi_window.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_toastr/flutter_toastr.dart';
import 'package:network_proxy/network/http/http.dart';
import 'package:network_proxy/network/http/http_headers.dart';
import 'package:network_proxy/network/http_client.dart';
import 'package:network_proxy/ui/component/split_view.dart';
class RequestEditor extends StatefulWidget {
final WindowController? windowController;
final HttpRequest? request;
const RequestEditor({super.key, this.request, this.windowController});
@override
State<StatefulWidget> createState() {
return RequestEditorState();
}
}
class RequestEditorState extends State<RequestEditor> {
final requestLineKey = GlobalKey<_RequestLineState>();
final requestKey = GlobalKey<_HttpState>();
ValueNotifier responseChange = ValueNotifier<bool>(false);
HttpResponse? response;
@override
void initState() {
super.initState();
RawKeyboard.instance.addListener(onKeyEvent);
}
void onKeyEvent(RawKeyEvent event) {
if (event.isKeyPressed(LogicalKeyboardKey.metaLeft) && event.isKeyPressed(LogicalKeyboardKey.keyW)) {
RawKeyboard.instance.removeListener(onKeyEvent);
widget.windowController?.close();
return;
}
}
@override
void dispose() {
RawKeyboard.instance.removeListener(onKeyEvent);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("发起请求", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
toolbarHeight: Platform.isWindows ? 36 : null,
centerTitle: true,
actions: [
TextButton.icon(
onPressed: () async => sendRequest(), icon: const Icon(Icons.send), label: const Text("发送")),
const SizedBox(width: 10)
],
),
body: Column(children: [
_RequestLine(key: requestLineKey, request: widget.request),
Expanded(
child: VerticalSplitView(
ratio: 0.53,
left: _HttpWidget(
key: requestKey,
title: const Text("Request", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
message: widget.request),
right: ValueListenableBuilder(
valueListenable: responseChange,
builder: (_, value, __) => _HttpWidget(
title: Row(children: [
const Text("Response", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)),
const Spacer(),
Text(response?.status.toString() ?? '', style: const TextStyle(fontSize: 14))
]),
message: response,
readOnly: true)),
)),
]));
}
///发送请求
sendRequest() async {
var currentState = requestLineKey.currentState!;
var headers = requestKey.currentState?.getHeaders();
var requestBody = requestKey.currentState?.getBody();
HttpRequest request = HttpRequest(HttpMethod.valueOf(currentState.requestMethod), currentState.requestUrl);
request.headers.addAll(headers);
request.body = requestBody?.codeUnits;
HttpClients.proxyRequest(request).then((response) {
this.response = response;
responseChange.value = !responseChange.value;
});
if (context.mounted) {
FlutterToastr.show('已发送请求', context);
}
}
}
class _HttpWidget extends StatefulWidget {
final HttpMessage? message;
final bool readOnly;
final Widget title;
const _HttpWidget({this.message, this.readOnly = false, super.key, required this.title});
@override
State<StatefulWidget> createState() {
return _HttpState();
}
}
class _HttpState extends State<_HttpWidget> {
final tabs = ['Header', 'Body'];
final headerKey = GlobalKey<HeadersState>();
String? body;
@override
void initState() {
super.initState();
}
String? getBody() {
return body;
}
HttpHeaders? getHeaders() {
return headerKey.currentState?.getHeaders();
}
@override
Widget build(BuildContext context) {
body = widget.message?.bodyAsString;
headerKey.currentState?.refreshHeader(widget.message?.headers);
if (widget.message == null && widget.readOnly) {
return Scaffold(appBar: AppBar(title: widget.title), body: const Center(child: Text("无数据")));
}
return SingleChildScrollView(
child: SizedBox(
height: MediaQuery.of(context).size.height - 120,
child: DefaultTabController(
length: tabs.length,
child: Scaffold(
primary: false,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(70),
child: AppBar(
title: widget.title,
bottom: TabBar(tabs: tabs.map((e) => Tab(text: e, height: 35)).toList()),
)),
body: Padding(
padding: const EdgeInsets.only(left: 10),
child: TabBarView(
children: [
Headers(key: headerKey, headers: widget.message?.headers, readOnly: widget.readOnly),
_body()
],
)),
))));
}
Widget _body() {
if (body != null && widget.readOnly && widget.message?.contentType == ContentType.json) {
try {
body = const JsonEncoder.withIndent(' ').convert(const JsonDecoder().convert(body!));
} catch (_) {}
}
return TextField(
autofocus: true,
controller: TextEditingController(text: body),
readOnly: widget.readOnly,
onChanged: (value) {
body = value;
},
minLines: 20,
maxLines: 20);
}
}
///请求行
class _RequestLine extends StatefulWidget {
final HttpRequest? request;
const _RequestLine({super.key, this.request});
@override
State<StatefulWidget> createState() {
return _RequestLineState();
}
}
class _RequestLineState extends State<_RequestLine> {
String requestUrl = "";
String requestMethod = HttpMethod.get.name;
@override
void initState() {
super.initState();
if (widget.request == null) {
return;
}
var request = widget.request!;
requestUrl = request.requestUrl;
requestMethod = request.method.name;
}
@override
Widget build(BuildContext context) {
return TextField(
decoration: InputDecoration(
prefix: DropdownButton(
padding: const EdgeInsets.only(right: 10),
underline: const SizedBox(),
isDense: true,
focusColor: Colors.transparent,
value: requestMethod,
items: HttpMethod.values.map((it) => DropdownMenuItem(value: it.name, child: Text(it.name))).toList(),
onChanged: (String? value) {
setState(() {
requestMethod = value!;
});
},
),
isDense: true,
border: const OutlineInputBorder(borderSide: BorderSide()),
enabledBorder: const OutlineInputBorder(borderSide: BorderSide(color: Colors.grey, width: 0.3))),
controller: TextEditingController(text: requestUrl),
onChanged: (value) {
requestUrl = value;
});
}
}
///请求头
class Headers extends StatefulWidget {
final HttpHeaders? headers;
final bool readOnly; //只读
const Headers({super.key, this.headers, this.readOnly = false});
@override
State<StatefulWidget> createState() {
return HeadersState();
}
}
class HeadersState extends State<Headers> with AutomaticKeepAliveClientMixin {
final Map<TextEditingController, List<TextEditingController>> _headers = {};
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
if (widget.headers == null) {
return;
}
widget.headers?.forEach((name, values) {
_headers[TextEditingController(text: name)] = values.map((it) => TextEditingController(text: it)).toList();
});
}
//刷新header
refreshHeader(HttpHeaders? headers) {
_headers.clear();
setState(() {
headers?.forEach((name, values) {
_headers[TextEditingController(text: name)] = values.map((it) => TextEditingController(text: it)).toList();
});
});
}
///获取所有请求头
HttpHeaders getHeaders() {
var headers = HttpHeaders();
_headers.forEach((name, values) {
if (name.text.isEmpty) {
return;
}
for (var element in values) {
if (element.text.isNotEmpty) {
headers.add(name.text, element.text);
}
}
});
return headers;
}
@override
Widget build(BuildContext context) {
super.build(context);
var list = [
_row(const Text('Key'), const Text('Value'), const Text('')),
..._buildRows(),
];
if (!widget.readOnly) {
list.add(TextButton(
child: const Text("添加Header", textAlign: TextAlign.center),
onPressed: () {
setState(() {
_headers[TextEditingController()] = [TextEditingController()];
});
},
));
}
return Scaffold(
body: Padding(
padding: const EdgeInsets.only(top: 10),
child: ListView.separated(
separatorBuilder: (context, index) =>
index == list.length ? const SizedBox() : const Divider(thickness: 0.2),
itemBuilder: (context, index) => list[index],
itemCount: list.length)));
}
List<Widget> _buildRows() {
List<Widget> list = [];
_headers.forEach((key, values) {
for (var val in values) {
list.add(_row(
_cell(key, isKey: true),
_cell(val),
widget.readOnly
? null
: Padding(
padding: const EdgeInsets.only(right: 15),
child: InkWell(
onTap: () {
setState(() {
_headers.remove(key);
});
},
child: const Icon(Icons.remove_circle, size: 16)))));
}
});
return list;
}
Widget _cell(TextEditingController val, {bool isKey = false}) {
return Container(
padding: const EdgeInsets.only(right: 5),
child: TextFormField(
readOnly: widget.readOnly,
style: TextStyle(fontSize: 12, fontWeight: isKey ? FontWeight.w500 : null),
controller: val,
minLines: 1,
maxLines: 3,
decoration: InputDecoration(isDense: true, border: InputBorder.none, hintText: isKey ? "Key" : "Value")));
}
Widget _row(Widget key, Widget val, Widget? op) {
return Row(children: [Expanded(flex: 4, child: key), Expanded(flex: 6, child: val), op ?? const SizedBox()]);
}
}