2022年12月30日 星期五
2022年12月16日 星期五
用 dart 語言寫簡易帶上傳功能的 http 檔案伺服器
import 'dart:convert';
import 'dart:io';
ContentType jsType = ContentType('application','javascript',charset:'UTF-8');
ContentType textType = ContentType('text','plain',charset:'UTF-8');
ContentType htmlType = ContentType('text','html',charset: 'UTF-8');
List<int> disposition = 'Content-Disposition:'.codeUnits;
List<int> multipart = 'Content-Type:'.codeUnits;
List<int> u8x2crlf = '\r\n\r\n'.codeUnits;
List<int> u8crlf__ = '\r\n--'.codeUnits;
const styleTag = '''<style>
body {background:black; color:green; font-size:16px}
a {margin-left:32px}
.buttonLike {background:black; color:dodgerblue; cursor:pointer}
.buttonLike:hover {background:gray; color:white}
</style>''';// css
const buttonJS = '''<input hidden id="pickOne" type="file">
<label for="pickOne" class="buttonLike" id="choose">上傳檔案 ?</label>
<button id="uploadButton" onclick="uploadFile()" disabled="true"></button><br>
<script type="application/javascript">
pickOne.addEventListener("change", (e)=> {
choose.innerHTML = e.target.files[0].name;
uploadButton.disabled = false;
uploadButton.innerHTML= "開始上傳";
});
const uploadFile = () => {
if (pickOne.files.length > 0) {
pickOne.disabled = true;
uploadButton.disabled = true;
uploadButton.textContent = "上傳中, 請稍候...";
const formData = new FormData();
formData.append("appAPK", pickOne.files[0]);
fetch("/postTMP", {method: "POST", body: formData})
.then((blob) => console.log("上傳成功."))
.catch((error) => console.log("上傳失敗!"))
.finally(() => location.reload());
}
}
</script>''';
const headTag = '<head><meta charset="UTF-8">$styleTag</head>';
const endbodyhtml = '</body></html>';
const htmlHere='<!DOCTYPE html><html>$headTag<body>It works.</body></html>';
int indexof(List<int> data, List<int> subList) {
int matchlength = subList.length;
int size = data.length;
int slider = 0;
while (slider < size) {
int endPos = slider + matchlength;
if (endPos > size) return -1;
int position = slider;
for (int i = 0; i < matchlength; i++) {
if (data[position] != subList[i]) break;
position ++;
}
if (endPos == position) return slider;
slider ++;
}
return -1;
}
String formParse (List<int> data, String key) {
List<String> args = utf8.decode(data).trim().split(';');
for (final pair in args) {
if (! pair.contains(key) || ! pair.contains('=')) continue;
int start = pair.indexOf('"');
int last = pair.lastIndexOf('"');
if (start >= 0 && last > 1) return pair.substring(start + 1, last);
start = pair.indexOf("'");
last = pair.lastIndexOf("'");
if (start >= 0 && last > 1) return pair.substring(start + 1, last);
return pair.split('=').last;
}
return '';
}
void postListen (HttpRequest req, String path) {
Directory uploadDir = Directory(path);
String? tag = req.headers.contentType?.parameters['boundary'];
if (tag == null) req.response.close();
else {
List<int> boundary = tag.codeUnits;
IOSink? sink = null;
req.listen (
(List<int> data) {
int start = indexof(data, boundary);
if (start < 0) sink ?. add(data);
else {
start += 2 + boundary.length;
List<int> form = data.sublist(start);
int offset = indexof(form, disposition);
if (offset >= 0) {
offset = indexof(form, u8x2crlf);
if (offset >= 0) {
start += offset + u8x2crlf.length;
int eos = indexof(form, multipart);
if (eos > 0) {
if (! uploadDir.existsSync()) {
uploadDir.createSync(recursive: true);
}
String name = formParse(form.sublist(0, eos), 'filename');
if (name.length > 0) {
sink = File('${uploadDir.path}/$name').openWrite();
}
}
}
}
final startData = data.sublist(start);
int size = indexof(startData, boundary);
if (size >= 0) {
if (indexof(startData, u8crlf__) > 0) size -= 4;
sink ?. add(data.sublist(start, start + size));
} else {
if (offset > 0) {
sink ?. add(startData);
} else {
size = indexof(data, boundary);
if (indexof(data, u8crlf__) > 0) size -= 4;
sink ?. add(data.sublist(0, size));
}
}
}
},
onError: (_) => req.response.close(),
onDone : ( ) {
req.response .. statusCode = 200 .. write('OK') .. close();
sink ?. close();
}
);
}
}
void getPipe(String pathname, HttpResponse reply) {
String fname = (pathname == '/index') ? './index.html' : '.$pathname';
final name = Uri.decodeFull(fname);
File file2Reply = File(name);
reply .. statusCode = 200 .. headers.contentType = htmlType;
if (! file2Reply.existsSync() && ! name.endsWith('/')) {
if (name == './hello' || name == './index.html') reply.write(htmlHere);
else reply .. statusCode = 404 ..
write('<!DOCTYPE html><html><body>404 Not found!</body></html>');
reply.close();
return;
}
if (name.endsWith('/')) {
final dir = Directory(name);
reply.write('<!DOCTYPE html><html>$headTag<body>$buttonJS 檔案清單:<br>');
if (dir.existsSync()) dir.listSync() ..
sort((a, b) {
if (a is Directory && b is File) return 1;
if (a is File && b is Directory) return -1;
return a.path.toLowerCase().compareTo(b.path.toLowerCase());
}) ..
forEach((FileSystemEntity e) {
final url = Uri.encodeFull(e.path.replaceFirst('.', ''));
final show = e.path.replaceFirst(name , '');
if (e is File) reply.write('<a href="$url"> 檔案 $show</a><br>');
if (e is Directory) reply.write('<a href="$url/"> 目錄 $show</a><br>');
});
reply .. write(endbodyhtml) .. close();
return;
}
reply ..
headers.contentLength = file2Reply.lengthSync() ..
headers.contentType = name.endsWith('.js') ? jsType :
RegExp(r'(\.htm|\.html)$').hasMatch(name) ? htmlType :
RegExp(r'(\.txt|\.dart|\.pem|\.c|\.h)$').hasMatch(name) ? textType :
name.endsWith('.css') ? ContentType('text', 'css') :
name.endsWith('.jpg') ? ContentType('image', 'jpg') :
name.endsWith('.png') ? ContentType('image', 'png') :
ContentType('application', 'octet-stream');
file2Reply.openRead().pipe(reply);
}
void main(List<String> args) {
final ip = InternetAddress.anyIPv4;
int port = args.length > 0 ? int.parse(args[0]) : 8080;
print('http://${ip.host}:$port');
HttpServer.bind(ip, port).then((HttpServer server){
server.listen((HttpRequest req) {
String pathname = req.uri.path;
if (req.method == 'GET') getPipe(pathname, req.response);
else if(pathname =='/postTMP') postListen(req, './上傳暫存區');
else req.response.close();
});
ProcessSignal.sigint.watch().listen((ProcessSignal signal) {// Ctrl + C
server.close();
exit(1);
});
});
}
2022年11月27日 星期日
用 dart 語言寫簡單的 SocketServer 聊天室
// server.dart
import 'dart:io';
import 'dart:convert';
class ChatRoom {
int key = 0;
static int sn = 0;
static Map<int, Socket> sockets = { };
void broadcast(List<int> data) {
sockets.forEach((id, session) {
if (key != id) session.add(utf8.encode('id$key:>${utf8.decode(data)}'));
});
}
void relay(List<int> data) async {
String text = utf8.decode(data).trim();
if (text == 'quit') {
print('relay: $text');
final session = sockets.remove(key);
await session?.close();
}
broadcast(data);
}
void shutdown( ) {
sockets.remove(key);
broadcast(utf8.encode('bye!\n'));
print('id$key leave, remain ${sockets.length}');
}
ChatRoom (Socket session) {
key = ++sn;
session .. add(utf8.encode('id${key} welcome!\n')) .. listen(
relay,
onDone: shutdown,
onError: (_) => session.close()
);
sockets[key] = session;
print('id$key join [total ${sockets.length}]');
}
}
void main( ) {
int port = 8080;
print("Chat room at $port");
ServerSocket.bind(InternetAddress.anyIPv4, port).then((ServerSocket server) {
server.listen((socket) => ChatRoom(socket));
});
}
使用 dart VM 來跑: dart server.dart, 之後就可以用 telnet 127.0.0.1 8080 讓客戶端連進聊天室, 只是 linux 上的 telnet client 在中文編/解碼上, 雖然接收時解中文碼沒問題, 但傳送時編成的中文碼可能會有問題, 幸好用 dart 語言編寫相同功能的 telnet 終端機不是難事:
// client.dart
import 'dart:io';
import 'dart:convert';
void main(List<String> args) async {
String ip = (args.length > 0) ? args[0] : 'localhost';
int port = (args.length > 1) ? int.parse(args[1]) : 8080;
Socket session = await Socket.connect(ip, port);
session.listen (
(dynamic data) => print((data is String) ? data : utf8.decode(data).trim()),
onDone: ( ) => exit(0),
onError: (_) => session.close()
);
stdin.listen (
(List<int> data) {
try {// print(data);
if (utf8.decode(data).trim() == 'quit') throw 'user quit';
else session.add(data);
} catch(e) {
print('stdin: $e, $data');
session.close();
exit(0);
}
}
);
}
同樣用 dart VM 來跑: dart client.dart
2022年11月25日 星期五
使用 crypto 產生密鑰並自我簽證
// sudo apt-get install libcrypto
// g++ genrsa.c -lcrypto && ./a.out
// genrsa.c
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <stdio.h>
int main( ) {
int nbits = 2048;
RSA *rsaKey = RSA_new();
BIGNUM *bignF4 = BN_new();
BN_set_word(bignF4, 0x10001);// bignF4 = 65537
if (RSA_generate_key_ex(rsaKey, nbits, bignF4, NULL)) {
long days = 365;
unsigned char* subj[3] = {
(unsigned char *)"TW",
(unsigned char *)"SELF",
(unsigned char *)"0.0.0.0"
};
X509 *certify = X509_new();
X509_set_version(certify, 2);// Ver 3
ASN1_INTEGER_set(X509_get_serialNumber(certify), 1);
X509_gmtime_adj(X509_get_notBefore(certify), 0);
X509_gmtime_adj(X509_get_notAfter(certify), days * 86400);
X509_NAME *subjName = X509_get_subject_name(certify);
X509_NAME_add_entry_by_txt(subjName, "C" , MBSTRING_ASC, subj[0], -1, -1, 0);
X509_NAME_add_entry_by_txt(subjName, "O" , MBSTRING_ASC, subj[1], -1, -1, 0);
X509_NAME_add_entry_by_txt(subjName, "CN", MBSTRING_ASC, subj[2], -1, -1, 0);
X509_set_issuer_name(certify, subjName);
EVP_PKEY *keyPair = EVP_PKEY_new();
EVP_PKEY_assign_RSA(keyPair, rsaKey);
X509_set_pubkey(certify, keyPair);
X509_sign(certify, keyPair, EVP_sha1());
FILE *fout = fopen("private.pem", "wb");
PEM_write_PrivateKey(fout, keyPair, NULL, NULL, 0, NULL, NULL);
fclose(fout);
EVP_PKEY_free(keyPair);
fout = fopen("certify.pem", "wb");
PEM_write_X509(fout, certify);
fclose(fout);
X509_free(certify);
}
BN_free(bignF4);
RSA_free(rsaKey);
return 0;
}
用 python 解簡單的常微分方程式
# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...
-
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 就字面意義來說 Solder Mask (防焊層)就是要把焊接(電氣)層遮掉, Paste Mask 則是要把護膠層遮掉,兩者使用時機並不同, 前者用在 PCB 製程, 後者用於主機板組立製程(PCBA)開鋼板. 2. 在 PCB 的製程中,一塊銅箔基板,為了要線路化,...