Files
proxypin/lib/ui/mobile/request/request_editor.dart
2023-09-18 01:41:43 +08:00

452 lines
14 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_toastr/flutter_toastr.dart';
import 'package:network_proxy/network/bin/server.dart';
import 'package:network_proxy/network/host_port.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/content/body.dart';
import 'package:network_proxy/utils/curl.dart';
class MobileRequestEditor extends StatefulWidget {
final HttpRequest? request;
final ProxyServer? proxyServer;
const MobileRequestEditor({super.key, this.request, required this.proxyServer});
@override
State<StatefulWidget> createState() {
return RequestEditorState();
}
}
class RequestEditorState extends State<MobileRequestEditor> with SingleTickerProviderStateMixin {
var tabs = const [
Tab(text: "请求"),
Tab(text: "响应"),
];
final requestLineKey = GlobalKey<_RequestLineState>();
final requestKey = GlobalKey<_HttpState>();
ValueNotifier responseChange = ValueNotifier<bool>(false);
late TabController tabController;
HttpRequest? request;
HttpResponse? response;
@override
void dispose() {
tabController.dispose();
responseChange.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
tabController = TabController(length: tabs.length, vsync: this);
request = widget.request;
if (widget.request == null) {
curlParse();
}
}
curlParse() async {
//获取剪切板内容
var data = await Clipboard.getData('text/plain');
if (data == null || data.text == null) {
return;
}
var text = data.text;
if (text?.trimLeft().startsWith('curl') == true && context.mounted) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(title: const Text('提示'), content: const Text('识别到剪切板内容是curl格式是否转换为HTTP请求'), actions: [
TextButton(child: const Text('取消'), onPressed: () => Navigator.of(context).pop()),
TextButton(
child: const Text('确定'),
onPressed: () {
try {
setState(() {
request = parseCurl(text!);
requestLineKey.currentState?.change(request?.uri, request?.method.name);
});
} catch (e) {
FlutterToastr.show('转换失败', context);
}
Navigator.of(context).pop();
}),
]);
},
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("发起请求", style: TextStyle(fontSize: 16)),
centerTitle: true,
leading: TextButton(
onPressed: () => Navigator.pop(context),
child: Text("取消", style: Theme.of(context).textTheme.bodyMedium)),
actions: [TextButton.icon(icon: const Icon(Icons.send), label: const Text("发送"), onPressed: sendRequest)],
bottom: TabBar(controller: tabController, tabs: tabs)),
body: TabBarView(
controller: tabController,
children: [
_HttpWidget(title: _RequestLine(request: request, key: requestLineKey), message: request, key: requestKey),
ValueListenableBuilder(
valueListenable: responseChange,
builder: (_, value, __) => _HttpWidget(
title: Row(children: [
const Text('状态码:', style: TextStyle(fontWeight: FontWeight.w500)),
const SizedBox(width: 10),
Text(response?.status.toString() ?? "", style: const TextStyle(color: Colors.blue))
]),
readOnly: true,
message: response)),
],
));
}
///发送请求
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;
var proxyInfo = widget.proxyServer?.isRunning == true ? ProxyInfo.of("127.0.0.1", widget.proxyServer!.port) : null;
HttpClients.proxyRequest(proxyInfo: proxyInfo, request).then((response) {
FlutterToastr.show('请求成功', context);
this.response = response;
this.response?.request = request;
tabController.animateTo(1);
responseChange.value = !responseChange.value;
}).catchError((e) {
FlutterToastr.show('请求失败$e', 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> with AutomaticKeepAliveClientMixin {
final headerKey = GlobalKey<HeadersState>();
String? body;
@override
bool get wantKeepAlive => true;
String? getBody() {
return body;
}
HttpHeaders? getHeaders() {
return headerKey.currentState?.getHeaders();
}
@override
Widget build(BuildContext context) {
super.build(context);
body = widget.message?.bodyAsString;
headerKey.currentState?.refreshHeader(widget.message?.headers);
if (widget.message == null && widget.readOnly) {
return const Center(child: Text("无数据"));
}
return SingleChildScrollView(
padding: const EdgeInsets.all(15),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
widget.title,
Headers(headers: widget.message?.headers, key: headerKey, readOnly: widget.readOnly), // 请求头
const SizedBox(height: 10),
const Text("Body", style: TextStyle(fontWeight: FontWeight.w500, color: Colors.blue)),
_body(),
const SizedBox(height: 10),
]));
}
Widget _body() {
if (widget.readOnly) {
return SingleChildScrollView(child: HttpBodyWidget(httpMessage: widget.message));
}
return TextField(
controller: TextEditingController(text: body),
readOnly: widget.readOnly,
onChanged: (value) {
body = value;
},
minLines: 3,
maxLines: 15);
}
}
class _RequestLine extends StatefulWidget {
final HttpRequest? request;
const _RequestLine({this.request, super.key});
@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) {
requestUrl = 'https://';
return;
}
var request = widget.request!;
requestUrl = request.requestUrl;
requestMethod = request.method.name;
}
change(String? requestUrl, String? requestMethod) {
this.requestUrl = requestUrl ?? this.requestUrl;
this.requestMethod = requestMethod ?? this.requestMethod;
}
@override
Widget build(BuildContext context) {
return TextField(
style: const TextStyle(fontSize: 14),
minLines: 1,
maxLines: 5,
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, style: const TextStyle(fontSize: 12))))
.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, required this.readOnly});
@override
State<StatefulWidget> createState() {
return HeadersState();
}
}
class HeadersState extends State<Headers> {
Map<String, List<String>> headers = {};
@override
void initState() {
super.initState();
if (widget.headers == null && !widget.readOnly) {
headers["User-Agent"] = ["ProxyPin/1.0.2"];
headers["Accept"] = ["*/*"];
return;
}
widget.headers?.forEach((name, values) {
headers[name] = values;
});
}
HttpHeaders getHeaders() {
var headers = HttpHeaders();
this.headers.forEach((key, values) {
if (key.isNotEmpty) {
headers.addValues(key, values);
}
});
return headers;
}
//刷新header
refreshHeader(HttpHeaders? headers) {
this.headers.clear();
headers?.forEach((name, values) {
this.headers[name] = values;
});
}
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
const SizedBox(
width: double.infinity,
child: Text("Headers", style: TextStyle(fontWeight: FontWeight.w500, color: Colors.blue))),
const SizedBox(height: 10),
...buildHeaders(),
widget.readOnly
? const SizedBox()
: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
modifyHeader("", "");
},
child: const Text("添加Header", textAlign: TextAlign.center))) //添加按钮
]));
}
List<Widget> buildHeaders() {
List<Widget> list = [];
headers.forEach((key, values) {
for (var val in values) {
var header = row(Text(key, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500)),
Text(val, style: const TextStyle(fontSize: 12), maxLines: 5, overflow: TextOverflow.ellipsis));
Widget headerWidget = Padding(padding: const EdgeInsets.only(top: 5, bottom: 5), child: header);
if (!widget.readOnly) {
headerWidget =
InkWell(onTap: () => modifyHeader(key, val), onLongPress: () => deleteHeader(key), child: headerWidget);
}
list.add(headerWidget);
list.add(const Divider(thickness: 0.2));
}
});
return list;
}
/// 修改请求头
modifyHeader(String key, String val) {
String headerName = key;
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
titlePadding: const EdgeInsets.only(left: 25, top: 10),
actionsPadding: const EdgeInsets.only(right: 10, bottom: 10),
title: const Text("修改请求头", style: TextStyle(fontSize: 18)),
content: Wrap(
children: [
TextField(
minLines: 1,
maxLines: 3,
controller: TextEditingController(text: headerName),
decoration: const InputDecoration(labelText: "请求头名称"),
onChanged: (value) {
headerName = value;
},
),
TextField(
minLines: 1,
maxLines: 8,
controller: TextEditingController(text: val),
decoration: const InputDecoration(labelText: "请求头值"),
onChanged: (value) {
val = value;
},
)
],
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("取消")),
TextButton(
onPressed: () {
setState(() {
if (headerName != key) {
headers.remove(key);
}
headers[headerName] = [val];
});
Navigator.pop(context);
},
child: const Text("修改")),
],
);
});
}
//删除
deleteHeader(String key) {
showDialog(
context: context,
builder: (ctx) {
return AlertDialog(
title: const Text("是否删除该请求头?", style: TextStyle(fontSize: 18)),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text("取消")),
TextButton(
onPressed: () {
setState(() {
headers.remove(key);
});
Navigator.pop(context);
},
child: const Text("删除")),
],
);
});
}
Widget row(Widget title, Widget child) {
return Row(children: [
Expanded(flex: 3, child: title),
const SizedBox(width: 10, child: Text(":", style: TextStyle(color: Colors.orange, fontWeight: FontWeight.w600))),
Expanded(
flex: 6,
child: child,
),
]);
}
}