カテゴリ: program

PHPではどうもだいたいJSONのサイズが30MB超えるあたりからメモリ不足エラーで読み込みに失敗するようなので対応。
本当はJSONをストリーム扱いしてSAXのように処理すれば良いのだけれど、PHPに疎いのでそれは将来の課題として、差し当たりデータファイルをPHPとしてincludeしていた時代に作った、デカい時はsqlite3も作成してPHP側ではそれを読み込む処理を復活させてそれを活用。
一応仕込みはしたので後は30MBを超えた時の挙動を見れば大丈夫だろう。
全部sqlite3にすれば良いという話もあるけれどそれだと反映に時間かかるんだよね。
PHP Web Services: APIs for the Modern Web
Mitchell, Lorna Jane
O'Reilly Media
2016-01-21


このエントリーをはてなブックマークに追加 mixiチェック

JSONを読んで内容を表示するプログラムなんだけど、
Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 2097160 bytes) 
というのが出た。
さてどうしよう…。DBとかは使いたくないしな…。JSONを分割?

https://qiita.com/P2eFR6RU/items/9370011fe6cdb884769f に色々小技が。

組み込みだから
ini_set("memory_limit", "512M");
は厳しそうだな…。

小技もともかく、抜本的には今の、でかい単一JSONを一気に読んで処理、を改める必要ありそう。
「最初の100行だけ読む」とかできないものか…考えるだに無理そうだけど…。1データ1行のテキストにするとか…。
XMLで言うところのSAXみたいな、JSON Streaming Parser for PHPを使えば良いのかしら…。
PHPは専門外だがまぁ何とかなるだろう…。


このエントリーをはてなブックマークに追加 mixiチェック

iperf3はUDPモードだとジッタを測定できる。
このジッタの値はどうやって求めているか?を調べざるを得なくなったので渋々調べた。
iperf3 jitter calculationによると、どうもサーバ側から送信時刻を含んだRTPの電文を16回送信して、以下のように求めているとのこと。

In Reporter.c of Iperf 2.0.3 , the "jitter" designed to follow RFC 1889, Real Time Protocol (RTP):

            // from RFC 1889, Real Time Protocol (RTP) 
            // J = J + ( | D(i-1,i) | - J ) / 16 
            transit = TimeDifference( packet->packetTime, packet->sentTime );
            if ( data->lastTransit != 0.0 ) {
                deltaTransit = transit - data->lastTransit;
                if ( deltaTransit < 0.0 ) {
                    deltaTransit = -deltaTransit;
                }
                stats->jitter += (deltaTransit - stats->jitter) / (16.0);
            }
            data->lastTransit = transit;

so the key is "RFC 1889, Real Time Protocol (RTP)"


現在時刻ー電文中の送信時刻で通信に掛かった時間を求め、前回の電文の時間と比べて揺らぎを求め、その揺らぎの絶対値/16を加算していく…。
つまるところ、ばらつきの絶対値を合計して16で割ってるから分散…というか二乗していないので最後に√を取る必要が無いので、これは標準偏差なのか。
毎回加算しているのが平均との差分なのかちょっと納得いかないが標準偏差なのはいかにもあり得る話。


このエントリーをはてなブックマークに追加 mixiチェック

GMOのWiMAX2の「三日間で10GBまで」縛り回避のため、夜間バッチのFTPで送るJSON群をZIPにまとめ、一旦OCI上のVMに送信して、VM側でZIPを再びJSONに戻してそれをWEBサーバにFTPする方式にした。我が家からの送信量は大体1/13になったので今後は大丈夫だろう。
これも18日にドコモ光が開通するまでの辛抱なのでローカル側のバッチで一発自動化まではしないでよかんべ。
ただVM側の処理は簡単なのでシェルにする。
lftpでリモートディレクトリをローカルディレクトリの状態に同期させるスクリプトが以下。
ディレクトリ名の最後に/を付けてしまうと不味い事になる(指定したディレクトリの下にその名前で新規作成されたり)ので注意
-------
open ftp://ユーザ名:パスワード@ホスト名
mirror --parallel=2 --only-existing -R ローカルディレクトリ名 リモートディレクトリ名
close
exit
------
FTPサ-バ構築ガイド (A nutshell handbook)
Adrian Nye
オライリー・ジャパン
1996-10-30


このエントリーをはてなブックマークに追加 mixiチェック

接続はできた。
投稿には「1 label has been placed on this content」というのが勝手につけられた。
「ポルノグラフィ」だそうな。マジか。
ともあれ、ラベルを付けるだけなら投稿しても問題無さげ?…かどうかは分からない。BANに備えて3月の、メアドだけでアカウントが作れた時期に10個位作っておくべきだった。

0408
0408b

このエントリーをはてなブックマークに追加 mixiチェック

TiwtterやMastodonと違い、BlueSkyのOGPカードに表示するタイトルや説明、画像はクライアントが準備してPOSTする時に沿えてやらねばならない。
その場合の画像のフォーマットはJPEGは駄目、PNGは大丈夫だった。JPEGデータだと以下のようなエラーが返ってくる。
BadRequestError: Response(success=False, status_code=400, content=XrpcError(error='InvalidMimeType', message='Wrong type of file. It is */* but it must match image/*.'), headers={'Date': 'Sat, 23 Mar 2024 14:55:59 GMT', 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': '96', 'Connection': 'keep-alive', 'X-Powered-By': 'Express', 'Access-Control-Allow-Origin': '*', 'RateLimit-Limit': '5000', 'RateLimit-Remaining': '4997', 'RateLimit-Reset': '1711209359', 'RateLimit-Policy': '5000;w=3600', 'ETag': 'W/"60-naTgCWHhjZ5FGkdXs552SxW2Nug"', 'Vary': 'Accept-Encoding'})

メモ:Pythonで画像フォーマットを変換するにはPillowを使う模様
https://note.nkmk.me/python-pillow-basic/


このエントリーをはてなブックマークに追加 mixiチェック

1.Nostrには「クライアントが表示する前にユーザにワンクッション置かせる指定」の規格NIP-36があるのでそれを設定する。
2.iPad用のメジャーなNostrクライアント、Damusは上記のNIP-36に対応しておらず、代わりにハッシュタグ「#nsfw」を見ているらしいので、それを付けるのが親切。
3.ハッシュタグ(NIP-12)は本文中にTwitter同様「#hoge」等と記載し、更にtag「t」を使う。tのvalueには「#」は含めない
4.画像を添付したい時は、インターネット上のどこかに画像をアップロードしてその画像のURIを本文中に含める

つまりはこう





このエントリーをはてなブックマークに追加 mixiチェック

先日Nostrへの書き込みに成功したのでボットをNostrに対応させた
リレーはよくわからんのでこの辺からを適当に羅列したものの中から毎回2つをランダムに選択して送信させているんだけど、同一イベントを例えば100カ所に送るとか駄目なのかしら。あるいは各クライアントのデフォルトに全部送るとか?ただしい作法がわからない。
あと、ハッシュタグに未対応なのでこれは対応しないとな。
まぁ…Nostrのノリが分からぬのでまぁ最初は静かにしておくべし。



このエントリーをはてなブックマークに追加 mixiチェック

Java製のクライアントを作ってNostrに投稿したい(既存のJava製のボットにNostr投稿機能を追加したいので調べた。
一応動作したのでNostrのJava製クライアントに関する日本語情報が少ない事だしここに記す。ちなみにエラー対応不足のため書き込みに失敗すると子スレッドが残ってしまう。
■1.nostr-javaのClientクラス
やれ~/.nostr-java配下にプロパティファイルを作れだのハンドラを作れだのmodule-info.javaを作れと喧しい上に実行したら
Exception in thread "main" java.lang.NoClassDefFoundError: org/bouncycastle/jce/provider/BouncyCastleProvider
と言われhttps://www.bouncycastle.org/latest_releases.htmlからjarをダウンロードして入れたり
java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targetr
と言われ実行時に
-Djavax.net.ssl.trustStore=C:\Program Files\Java\jdk-21.0.1\lib\security\cacerts
を入れたはいいがその後もトラブルが起きて解決できず。

■2.JavaでNostrのリレーに1件だけテキストイベントを投稿するコードを書いてみた
…nostr-javaのサンプルよりは分かりやすいけれど、まずビルドが通らない。多分JettyのWebSocketのライブラリとHTTPClientのライブラリのバージョンの不一致あたりだと思うけどそもそもimport文も省略されているので想像で入れたりしたけれど結局通らず。

■3.Jetty 12.x のWebSocketClientのサンプルコードを育てて自作
2番はビルドできないとは言え大いに参考にしつつ、まずはビルドや実行が通るように慎重に本家Jettyのサンプルから育てていく。
とりあえず最後まで動作して相手リレーサーバから
["OK","fdf83a3115ae12c3331acd6390641a2ab075b85bf21a44d7e10ce4d0379b0dc6",true,""]
なる電文が返って来たしNostterにも表示されたので動作はした模様
0103c

ちなみにWebSocketのクローズの辺りは少々あやしい。
NostrSendMain.java
/**
 * Nostr書き込み処理サンプル。
 */
package nekora.nostr;

import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.websocket.api.Callback;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.client.WebSocketClient;

import nostr.base.PrivateKey;
import nostr.base.PublicKey;
import nostr.crypto.schnorr.Schnorr;
import nostr.util.NostrUtil;

/**
 * 
 */
public class NostrSendMain {
	//final String RELAY_URL = "wss://relay.nostr.wirednet.jp";
	final String RELAY_URL = "wss://relay-jp.nostr.wirednet.jp";
	final String CONTENT = "初カキコ…ども…from My Java Client";

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		NostrSendMain my = new NostrSendMain();
		my.exec(args);
	}

	private void exec(String[] args) {
		// Use a standard, HTTP/1.1, HttpClient.
		HttpClient httpClient = new HttpClient();

		// Create and start WebSocketClient.
		WebSocketClient webSocketClient = new WebSocketClient(httpClient);
		try {
			webSocketClient.start();
			// The client-side WebSocket EndPoint that
			// receives WebSocket messages from the server.
			MyAutoDemandListenerEndPoint myendp = new MyAutoDemandListenerEndPoint();
			// The server URI to connect to.
			URI serverURI = URI.create(RELAY_URL);

			// クライアントエンドポイントをサーバーに接続.
			CompletableFuture clientSessionPromise = webSocketClient.connect(myendp, serverURI);

			// 接続処理完了を待つ
			// Thread.sleep(1500); // これ要らない気がするので削除してみた

			// セッションを取得
			try (Session session = clientSessionPromise.join()) {
				if (!session.isOpen()) {
					System.err.println("接続できませんでした");
					return;
				}
				System.out.println("接続OK…" + session);

				// Nonstr特化通信
				nostrExec(session);
				System.out.println("Nostrに送信完了");

			} catch (Exception e) {
				throw e;
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// クローズ
			try {
				webSocketClient.stop();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		System.out.println("すべて完了");
	}

	/**
	 * Nostrリレーサーバと通信 https://zenn.dev/memory_of_snow/articles/1f0ddd964c5f83 参考
	 * 
	 * @param webSocketClient
	 * @param session
	 */
	private void nostrExec(Session session) {
		// テスト用秘密鍵(=アカウント)をランダムに作成。
		PrivateKey secKey = new PrivateKey(Schnorr.generatePrivateKey());
		//PrivateKey secKey = new PrivateKey("アカウント固定する場合はエンコードされた秘密鍵文字列");

		try {
			// 秘密鍵から公開鍵を作成
			PublicKey publicKey = new PublicKey(Schnorr.genPubKey(secKey.getRawData()));
			String publicKeyHex = publicKey.toString();

			System.out.println("SecKey(HEX) = " + secKey);
			System.out.println("publicKey(HEX) = " + publicKey);

			long created_at = Instant.now().getEpochSecond();
			int kind = 1;
			List> tags = new ArrayList<>();

			// id計算に使う要素を集める
			// [0,"公開鍵(HEX)",投稿時間,kind(プレーンテキスト投稿は1),タグ群,"投稿内容"]
			String partsForId = "[0,\"" + publicKeyHex + "\"," + created_at + "," + kind + "," + tags + ",\"" + CONTENT
					+ "\"]";
			byte[] idSourceBytes = partsForId.getBytes(StandardCharsets.UTF_8);
			String id = NostrUtil.bytesToHex(NostrUtil.sha256(idSourceBytes));
			byte[] signedHashedSerializedEvent = Schnorr.sign(NostrUtil.sha256(idSourceBytes), secKey.getRawData(),
					NostrUtil.createRandomByteArray(32));
			String sig = NostrUtil.bytesToHex(signedHashedSerializedEvent);

			// メッセージは["EVENT",{
			// "id":"id",
			// "pubkey":"公開鍵(Hex)",
			// "created_at":作成時間(UnixTimeStamp),
			// "kind":kind(プレーンテキストは1),
			// "tags":[],
			// "content":"投稿内容",
			// "sig":"署名"
			// }]
			String message = "[\"EVENT\",{\"id\":\"" + id + "\",\"pubkey\":\"" + publicKeyHex + "\","
					+ "\"created_at\":" + created_at + ",\"kind\":" + kind + ",\"tags\":" + tags + ","
					+ "\"content\":\"" + CONTENT + "\",\"sig\":\"" + sig + "\"}]";

			String displayMessage = message.replace(",", ",\n");
			System.out.println("組み立てたメッセージ:\n" + displayMessage);

			// session.getRemote().sendString(message); // こんなメソッドは無いと言われる
			MySendCallBack calbak = new MySendCallBack(message);
			session.sendText(message, calbak);

			// 受信時間稼ぎ
			Thread.sleep(1500); // これ必要なのか分からないがサンプルに従い残す。

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
MyAutoDemandListenerEndPoint.java
package nekora.nostr;

import org.eclipse.jetty.websocket.api.Session;

/**
 * 接続用エンドポイント 以下をコピペ
 * https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-client-http
 */

public class MyAutoDemandListenerEndPoint implements Session.Listener.AutoDemanding {
	private Session session;

	@Override
	public void onWebSocketOpen(Session session) {
		this.session = session;
		// No need to demand here, because this endpoint is auto-demanding.
	}

	@Override
	public void onWebSocketText(String message) {
		System.out.println("受信メッセージ=" + message);
		// No need to demand here, because this endpoint is auto-demanding.
	}

}
MySendCallBack.java
package nekora.nostr;

import org.eclipse.jetty.websocket.api.Callback;

/**
 * 送信用コールバック
 * https://eclipse.dev/jetty/documentation/jetty-12/programming-guide/index.html#pg-websocket-session-send
 */
public class MySendCallBack implements Callback {
	private String message;

	public MySendCallBack(String message) {
		this.message = message;
	}

	@Override
	public void succeed() {
		System.out.println("テキスト送信成功." + message);
	}

	@Override
	public void fail(Throwable x) {
		// No need to rethrow or close the session.
		System.out.println("テキスト送信失敗." + message + " エラー=" + x.getMessage());
	}

}
ビルド時にIvy.xmlに指定するのは以下のみ(Mavenならpom.xml)。
dependency org="org.eclipse.jetty.websocket" name="jetty-websocket-jetty-client" rev="12.0.5"
あと、nostr-javaも使っているので事前にimportして、参照させる必要がある。
0104

自分の秘密鍵をhexにしたい時は下記を使う
https://nak.nostr.com/



このエントリーをはてなブックマークに追加 mixiチェック

クローリングする先のサイトのHTML変更に合わせて解析箇所も随時追従しないといけないんだけどサボってた。
HTMLは直接解析は中々厳しいので、HTMLCleanerで一旦XMLにして、それを解析するのが楽。
Pythonは数人以上でとりくむような代物だとやってられないけれど、この程度の処理だとJavaよりも楽ですな。全部設定ファイルなので引数を受け取る処理が先ず不要になるし。

このエントリーをはてなブックマークに追加 mixiチェック

↑このページのトップヘ