tag:blogger.com,1999:blog-92890962024-03-18T08:20:30.134+08:00cctgyaocl in conflict / complicationcctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.comBlogger827125tag:blogger.com,1999:blog-9289096.post-53604250124345305332024-03-18T08:19:00.003+08:002024-03-18T08:19:47.574+08:00Guava Collection 1 Immutable Collections<h2 class="atx" id="immutable-collections">Immutable Collections</h2>
<p>ref: <a href="https://github.com/google/guava/wiki/ImmutableCollectionsExplained">ImmutableCollectionsExplained · google/guava Wiki · GitHub</a></p>
<p>collection 的資料不一定需要隨時可以修改,如果可以任意修改,反而在某些時候,會造成問題。</p>
<ol>
<li><p>不能修改的 collection,將資料傳給 untrusted libraries 使用,也不會被 library 任意修改資料</p>
</li>
<li><p>thread-safe,再多執行緒環境共用資料時,可避免同時修改發生問題</p>
</li>
<li><p>可節省 time, space</p>
</li>
</ol>
<p>JDK 也有提供 <code>Collections.unmodifiableXXX</code> methods,但還是有可能有以下問題</p>
<ol>
<li><p>unwieldy and verbose: 使用時必須要在每一個地方,都先產生一份</p>
</li>
<li><p>unsafe: 只有在沒有其他地方,有儲存原本 collection 的 reference 時,這個 collection 才會是 immutable</p>
</li>
<li><p>inefficient: 資料結構還是跟原本 mutable collection 一樣,所以還是會有 concurrent modification check, 額外耗費的 space ...</p>
</li>
</ol>
<pre><code class="fenced-code-block language-java"> @Test
public void jdk_unmodifiableMethod() {
List list = new ArrayList();
list.add("item1");
list.add("item2");
List unmodifiableList = Collections.unmodifiableList(list);
Exception exception = assertThrows(UnsupportedOperationException.class, () -> {
unmodifiableList.add("item3");;
});
assertEquals(exception.getClass().getName(), "java.lang.UnsupportedOperationException");
String actualMessage = exception.getMessage();
assertNull(actualMessage);
// 如果修改原本的 list,還是會影響到 unmodifiableList
list.add("item3");
assertEquals(unmodifiableList.size(), 3);
}
@Test
public void guava_immutable() {
List<String> stringArrayList = Lists.newArrayList("item1","item2");
ImmutableList<String> immutableList = ImmutableList.copyOf(stringArrayList);
Exception exception = assertThrows(UnsupportedOperationException.class, () -> {
immutableList.add("item3");
});
assertEquals(exception.getClass().getName(), "java.lang.UnsupportedOperationException");
String actualMessage = exception.getMessage();
assertNull(actualMessage);
// 如果修改原本的 list,不會影響到 immutableList
stringArrayList.add("item3");
assertEquals(stringArrayList.size(), 3);
assertEquals(immutableList.size(), 2);
}</code></pre>
<p>Guava immutable collection 不能使用 null values,因為大部分的 code,都是必須要有值</p>
<p>產生 <code>ImmutableXXX</code> collection 的方法:</p>
<ol>
<li><p>使用 copyOf method ex: <code>ImmutableSet.copyOf(set)</code></p>
</li>
<li><p>使用 of method ex: <code>ImmutableSet.of("a", "b", "c")</code> or <code>ImmutableMap.of("a", 1, "b", 2)</code></p>
</li>
<li><p>使用 Builder</p>
</li>
</ol>
<pre><code class="fenced-code-block language-java"> @Test
public void immutableCollection() {
// copyOf
List<String> stringArrayList = Lists.newArrayList("item1","item2");
ImmutableList<String> immutableList = ImmutableList.copyOf(stringArrayList);
// of
ImmutableSet.of("a", "b", "c", "a", "d", "b");
// Builder
Color color1 = new Color(0, 0, 255);
Color color2 = new Color(0, 255, 0);
ImmutableSet<Color> colors = ImmutableSet.of(color1, color2);
ImmutableSet<Color> newcolors =
ImmutableSet.<Color>builder()
.addAll(colors)
.add(new Color(0, 191, 255))
.build();
}</code></pre>
<p>所有 immutable collections 都有透過 asList() 產生的 ImmutableList 的 view。</p>
<pre><code class="fenced-code-block language-java"> @Test
public void asList() {
ImmutableSet<String> set = ImmutableSet.of("a", "b", "c", "a", "d", "b");
String item0 = set.asList().get(0);
assertEquals(item0, "a");
}</code></pre>
<table>
<thead>
<tr>
<th>Interface</th>
<th>JDK or Guava?</th>
<th>Immutable Version</th>
</tr>
</thead>
<tbody><tr>
<td><code>Collection</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableCollection.html"><code>ImmutableCollection</code></a></td>
</tr>
<tr>
<td><code>List</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableList.html"><code>ImmutableList</code></a></td>
</tr>
<tr>
<td><code>Set</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableSet.html"><code>ImmutableSet</code></a></td>
</tr>
<tr>
<td><code>SortedSet</code>/<code>NavigableSet</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableSortedSet.html"><code>ImmutableSortedSet</code></a></td>
</tr>
<tr>
<td><code>Map</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableMap.html"><code>ImmutableMap</code></a></td>
</tr>
<tr>
<td><code>SortedMap</code></td>
<td>JDK</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableSortedMap.html"><code>ImmutableSortedMap</code></a></td>
</tr>
<tr>
<td><a href="https://github.com/google/guava/wiki/NewCollectionTypesExplained#Multiset"><code>Multiset</code></a></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableMultiset.html"><code>ImmutableMultiset</code></a></td>
</tr>
<tr>
<td><code>SortedMultiset</code></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/12.0/api/docs/com/google/common/collect/ImmutableSortedMultiset.html"><code>ImmutableSortedMultiset</code></a></td>
</tr>
<tr>
<td><a href="https://github.com/google/guava/wiki/NewCollectionTypesExplained#Multimap"><code>Multimap</code></a></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableMultimap.html"><code>ImmutableMultimap</code></a></td>
</tr>
<tr>
<td><code>ListMultimap</code></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableListMultimap.html"><code>ImmutableListMultimap</code></a></td>
</tr>
<tr>
<td><code>SetMultimap</code></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableSetMultimap.html"><code>ImmutableSetMultimap</code></a></td>
</tr>
<tr>
<td><a href="https://github.com/google/guava/wiki/NewCollectionTypesExplained#BiMap"><code>BiMap</code></a></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableBiMap.html"><code>ImmutableBiMap</code></a></td>
</tr>
<tr>
<td><a href="https://github.com/google/guava/wiki/NewCollectionTypesExplained#ClassToInstanceMap"><code>ClassToInstanceMap</code></a></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableClassToInstanceMap.html"><code>ImmutableClassToInstanceMap</code></a></td>
</tr>
<tr>
<td><a href="https://github.com/google/guava/wiki/NewCollectionTypesExplained#Table"><code>Table</code></a></td>
<td>Guava</td>
<td><a href="https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/ImmutableTable.html"><code>ImmutableTable</code></a></td>
</tr>
</tbody></table>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-71009208433544858742024-03-11T08:26:00.001+08:002024-03-11T08:26:06.146+08:00Guava in Java - ObjectUtilities<h2 class="atx" id="基本工具">基本工具</h2>
<h3 class="atx" id="commonobjectutilities">CommonObjectUtilities</h3>
<h4 class="atx" id="equals">equals</h4>
<p>當使用 Object.equals 時,如果遇到某個物件為 null,就會發生問題</p>
<p>com.google.common.base.Objects.equal 可做 null Object 的比較</p>
<p>JDK 提供類似的 java.util.Objects.equals()</p>
<pre><code class="fenced-code-block language-java"> @Test
public void equals() {
com.google.common.base.Objects.equal("a", "a"); // returns true
com.google.common.base.Objects.equal(null, "a"); // returns false
com.google.common.base.Objects.equal("a", null); // returns false
com.google.common.base.Objects.equal(null, null); // returns true
// JDK 提供類似的 java.util.Objects.equals()
java.util.Objects.equals("a", "a"); // returns true
java.util.Objects.equals(null, "a"); // returns false
java.util.Objects.equals("a", null); // returns false
java.util.Objects.equals(null, null); // returns true
String a = null;
String b = "b";
Exception exception = assertThrows(NullPointerException.class, () -> {
a.equals(b);
});
Exception exception2 = assertThrows(NullPointerException.class, () -> {
b.equals(a);
});
}</code></pre>
<h4 class="atx" id="hashcode">hashCode</h4>
<p>簡化 hashCode 的做法,可直接根據多個 fields 產生 hash</p>
<pre><code class="fenced-code-block language-java"> @Test
public void hashCodeTest() {
InnerClass innerClass = new InnerClass();
int hash1 = innerClass.hashCode();
int hash2 = innerClass.hashCode2();
assertEquals(hash1, hash2);;
}
public static class InnerClass {
private String a="a";
private String b="b";
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(a, b);
}
// JDK 有對應類似的 java.util.Objects.hash
public int hashCode2() {
return java.util.Objects.hash(a, b);
}
}</code></pre>
<h4 class="atx" id="tostring">toString</h4>
<p>利用 MoreObjects.toStringHelper() 簡化 toString</p>
<pre><code class="fenced-code-block language-java"> @Test
public void toStringTest() {
InnerClass2 cls = new InnerClass2();
String clsString = cls.toString();
// System.out.printf("clsString=%s%n", clsString);
assertEquals(clsString, "InnerClass2{a=a, b=2, x=1}");;
}
public static class InnerClass2 {
private String a="a";
private int b=2;
@Override
public String toString() {
return com.google.common.base.MoreObjects.toStringHelper(this)
.add("a", a)
.add("b", b)
.add("x", 1)
.toString();
}
}</code></pre>
<h4 class="atx" id="comparecompareto">compare/compareTo</h4>
<p>guava 提供 ComparisonChain,他是 fluent Comparator 可改善 compareTo 的寫法</p>
<pre><code class="fenced-code-block language-java"> class Person implements Comparable<Person> {
private String lastName;
private String firstName;
private int zipCode;
public int compareTo(Person that) {
return ComparisonChain.start()
.compare(this.firstName, that.firstName)
.compare(this.lastName, that.lastName)
.compare(this.zipCode, that.zipCode, Ordering.natural().nullsLast())
.result();
}
public int compareTo2(Person other) {
int cmp = lastName.compareTo(other.lastName);
if (cmp != 0) {
return cmp;
}
cmp = firstName.compareTo(other.firstName);
if (cmp != 0) {
return cmp;
}
return Integer.compare(zipCode, other.zipCode);
}
}</code></pre>
<h3 class="atx" id="throwable">Throwable</h3>
<pre><code class="fenced-code-block language-java">package guava.basic;
import java.util.List;
import com.google.common.base.Throwables;
public class ThrowableTest {
public static void main(String[] args) {
ThrowableTest tester = new ThrowableTest();
try {
System.out.println("invalidInputExceptionTest");
tester.invalidInputExceptionTest();
} catch (InvalidInputException e) {
//get the root cause
System.out.println("invalidInputExceptionTest getRootCause");
System.out.println(Throwables.getRootCause(e));
} catch (Exception e) {
//get the stack trace in string format
System.out.println("invalidInputExceptionTest getStackTraceAsString");
System.out.println(Throwables.getStackTraceAsString(e));
}
System.out.println("");
try {
System.out.println("indexOutOfBoundsExceptionTest");
tester.indexOutOfBoundsExceptionTest();
} catch (Exception e) {
System.out.println("indexOutOfBoundsExceptionTest getStackTraceAsString");
List<Throwable> elist = Throwables.getCausalChain(e);
for( Throwable t1: elist ) {
System.out.println(Throwables.getStackTraceAsString(t1));
}
// System.out.println(Throwables.getStackTraceAsString(e));
}
}
public void invalidInputExceptionTest() throws InvalidInputException {
try {
sqrt(-1.0);
} catch (Throwable e) {
//check the type of exception and throw it
Throwables.propagateIfInstanceOf(e, InvalidInputException.class);
// Throws throwable as-is only if it is a RuntimeException or an Error.
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
public void indexOutOfBoundsExceptionTest() {
try {
int[] data = {1, 2, 3};
getValue(data, 4);
} catch (Throwable e) {
Throwables.propagateIfInstanceOf(e, IndexOutOfBoundsException.class);
Throwables.throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
public double sqrt(double input) throws InvalidInputException {
if (input < 0) throw new InvalidInputException();
return Math.sqrt(input);
}
public double getValue(int[] list, int index) throws IndexOutOfBoundsException {
return list[index];
}
}
class InvalidInputException extends Exception {
}</code></pre>
<p>執行結果</p>
<pre><code class="fenced-code-block language-java">invalidInputExceptionTest
invalidInputExceptionTest getRootCause
com.maxkit.guava.InvalidInputException
indexOutOfBoundsExceptionTest
indexOutOfBoundsExceptionTest getStackTraceAsString
java.lang.ArrayIndexOutOfBoundsException: Index 4 out of bounds for length 3
at com.maxkit.guava.ThrowableTest.getValue(ThrowableTest.java:71)
at com.maxkit.guava.ThrowableTest.indexOutOfBoundsExceptionTest(ThrowableTest.java:57)
at com.maxkit.guava.ThrowableTest.main(ThrowableTest.java:31)</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://github.com/google/guava/wiki">Home · google/guava Wiki · GitHub</a></p>
<p><a href="https://www.baeldung.com/guava-guide">Guava Guide | Baeldung</a></p>
<p><a href="https://tobebetterjavaer.com/common-tool/guava.html#_01%E3%80%81%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F">Guava:Google开源的Java工具库,太强大了 | Java程序员进阶之路</a></p>
<p><a href="https://hackmd.io/@EasyEatWeb/r16sCXqSv">Google Guava 工具類 的介紹和使用 - HackMD</a></p>
<p><a href="https://wizardforcel.gitbooks.io/guava-tutorial/content/1.html"># Google Guava官方教程</a></p>
<p><a href="https://openhome.cc/Gossip/CodeData/GuavaTutorial/">專欄文章:Guava 教學</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-48007906864390591972024-03-04T08:19:00.001+08:002024-03-04T08:19:02.703+08:00Guava Basic Preconditions<p>Guava 是 Google 開發的 Java 開源工具 Library。內容主要有兩個部分:擴充 Java Collection Framework,提供更好用的 function,例如cache, range,以及 hash function</p>
<p>使用 Guava 只需要在 maven pom.xml 直接加入 dependency 即可</p>
<pre><code class="fenced-code-block language-xml"><dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
<!-- or, for Android: -->
<!-- <version>32.1.2-android</version> -->
</dependency></code></pre>
<h2 class="atx" id="基本工具">基本工具</h2>
<h3 class="atx" id="null-check">Null Check</h3>
<p>JDK 跟 Guava 都有針對 null check 問題提供了 Optional 類別來解決</p>
<p><a href="https://blog.csdn.net/qq_42105629/article/details/102458804">Optional,Gauva工具及和Java8中实现的区别_Tonels的博客-CSDN博客</a></p>
<p>java.util.Optional 是 final 的類別,無法被繼承</p>
<p>com.google.common.base.Optional 是 abstract class,有實作 Serializable</p>
<p>兩種 Optional 基本的使用方式差不多</p>
<pre><code class="fenced-code-block language-java"> com.google.common.base.Optional<Integer> possible = com.google.common.base.Optional.of(5);
boolean isPresent = possible.isPresent(); // returns true
// int val = possible.get(); // returns 5
int val = possible.or(-1); // returns 5
System.out.printf("isPresent=%b, val=%d%n", isPresent, val);
com.google.common.base.Optional<Integer> possible2 = com.google.common.base.Optional.fromNullable(null);
isPresent = possible2.isPresent();
val = possible2.or(-1);
System.out.printf("isPresent=%b, val=%d%n", isPresent, val);
java.util.Optional<Integer> opt = java.util.Optional.of(5);
boolean isPresent2 = opt.isPresent();
// int val2 = opt.get();
int val2 = opt.orElse(-1);
System.out.printf("isPresent2=%b, val2=%d%n", isPresent2, val2);
java.util.Optional<Integer> opt2 = java.util.Optional.ofNullable(null);
isPresent2 = opt2.isPresent();
val2 = opt2.orElse(-1);
System.out.printf("isPresent2=%b, val2=%d%n", isPresent2, val2);</code></pre>
<h3 class="atx" id="preconditions">Preconditions</h3>
<p>有很多 static method 可檢查 method 或 constructor 是否有正確的參數數值,如果檢查結果失敗,就會 throw exception</p>
<p>每一種 Preconditions 的 static method 都有三種變化</p>
<ol>
<li><p>No arguments,exception 裡面沒有 error message</p>
</li>
<li><p>有一個 Object argument,作為 error message,丟出的 exception 裡面會有 error message</p>
</li>
<li><p>有一個 String argument,搭配任意數量的 Object arguments,作為 error message 的 placeholder,類似 printf</p>
</li>
</ol>
<p>Preconditions 的 <em>checkArgument</em> 可檢查參數的正確性,失敗時會丟出 IllegalArgumentException</p>
<h4 class="atx" id="沒有-error-messge">沒有 error messge</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkArgument_without_error_message() {
int age = -18;
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
Preconditions.checkArgument(age > 0);
});
// String expectedMessage = null;
String actualMessage = exception.getMessage();
assertNull(actualMessage);
}</code></pre>
<h4 class="atx" id="有-error-message">有 error message</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkArgument_with_error_message() {
int age = -18;
String message = "Age can't be zero or less than zero.";
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
Preconditions.checkArgument(age > 0, message);
});
String expectedMessage = message;
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);
}</code></pre>
<h4 class="atx" id="有-error-message-template">有 error message template</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkArgument_with_template_error_message() {
int age = -18;
String message = "Age should be positive number, you supplied %s.";
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
Preconditions.checkArgument(age > 0, message, age);
});
String expectedMessage = String.format(message, age);
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);;
}</code></pre>
<h4 class="atx" id="checkelementindex">checkElementIndex</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkElementIndex() {
int[] numbers = { 1, 2, 3, 4, 5 };
String message = "Please check the bound of an array and retry";
Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> {
Preconditions.checkElementIndex(6, numbers.length - 1, message);
});
// expectedMessage: Please check the bound of an array and retry (6) must be less than size (4)
String expectedMessage = String.format(message+" (%d) must be less than size (%d)", 6, numbers.length - 1);
// System.out.printf("expectedMessage=%s%n", expectedMessage);
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);;
}</code></pre>
<h4 class="atx" id="checknotnull">checkNotNull</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkNotNull () {
String nullObject = null;
String message = "Please check the Object supplied, its null!";
Exception exception = assertThrows(NullPointerException.class, () -> {
Preconditions.checkNotNull(nullObject, message);
});
String expectedMessage = message;
// System.out.printf("expectedMessage=%s%n", expectedMessage);
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);;
}</code></pre>
<h4 class="atx" id="checkpositionindex">checkPositionIndex</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkPositionIndex() {
int[] numbers = { 1, 2, 3, 4, 5 };
String message = "Please check the bound of an array and retry";
Exception exception = assertThrows(IndexOutOfBoundsException.class, () -> {
Preconditions.checkPositionIndex(6, numbers.length - 1, message);
});
// expectedMessage: Please check the bound of an array and retry (6) must not be greater than size (4)
String expectedMessage = String.format(message+" (%d) must not be greater than size (%d)", 6, numbers.length - 1);
// System.out.printf("expectedMessage=%s%n", expectedMessage);
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);;
}</code></pre>
<h4 class="atx" id="checkstate">checkState</h4>
<pre><code class="fenced-code-block language-java"> @Test
public void checkState() {
int[] validStates = { -1, 0, 1 };
int givenState = 10;
String message = "You have entered an invalid state";
Exception exception = assertThrows(IllegalStateException.class, () -> {
Preconditions.checkState(
Arrays.binarySearch(validStates, givenState) > 0, message);
});
String expectedMessage = message;
String actualMessage = exception.getMessage();
assertEquals(expectedMessage, actualMessage);;
}</code></pre>
<h1 class="atx" id="references">References</h1>
<p><a href="https://github.com/google/guava/wiki">Home · google/guava Wiki · GitHub</a></p>
<p><a href="https://www.baeldung.com/guava-guide">Guava Guide | Baeldung</a></p>
<p><a href="https://tobebetterjavaer.com/common-tool/guava.html#_01%E3%80%81%E5%89%8D%E4%B8%96%E4%BB%8A%E7%94%9F">Guava:Google开源的Java工具库,太强大了 | Java程序员进阶之路</a></p>
<p><a href="https://hackmd.io/@EasyEatWeb/r16sCXqSv">Google Guava 工具類 的介紹和使用 - HackMD</a></p>
<p><a href="https://wizardforcel.gitbooks.io/guava-tutorial/content/1.html"># Google Guava官方教程</a></p>
<p><a href="https://openhome.cc/Gossip/CodeData/GuavaTutorial/">專欄文章:Guava 教學</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-76253901085736941922024-02-26T08:20:00.002+08:002024-02-26T08:20:12.249+08:00Well-known URI<p>well-known URI 是網站上以 <code>/.well-known/</code> 開始的網頁路徑,例如 <a href="https://www.example.com/.well-known/">https://www.example.com/.well-known/</a> 這樣的路徑。Well-known URI 是在 RFC 8615 定義,目的是在不同 http server 之間,提供該 http server 相關的一些資訊。</p>
<p>目前最常見的使用方式有兩種,第一種是在申請 http server SSL 憑證的時候,憑證中心會發送一個檔案給申請者,請申請者將該檔案放到 .well-known 的某個路徑下,讓憑證中心可以檢查確認憑證申請者確實有服務該網域的 http server 的所有權。</p>
<p>例如 Let's Encrypt 就是將驗證的檔案放在這個路徑下面 <code>/.well-known/acme-challenge/</code>,這是給 ACME (Automatic Certificate Management Environment) 使用的</p>
<p>第二種是給 Android iOS 手機使用的</p>
<ul>
<li><p><code>/.well-known/assetlinks.json</code> 是 Digital Asset Links,可告訴Android browser,要使用什麼 APP 打開</p>
</li>
<li><p><code>/.well-known/apple-app-site-association</code> Universal Links,是 iOS 使用的</p>
</li>
</ul>
<p><a href="https://github.com/moul/awesome-well-known">GitHub - moul/awesome-well-known: A curated list of well-known URIs, resources, guides and tools (RFC 5785)</a> 這個網頁提供了 .well-known 網址的使用列表</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://en.wikipedia.org/wiki/Well-known_URI">Well-known URI - Wikipedia</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-72704705645694466002024-02-19T08:22:00.000+08:002024-02-19T08:22:02.392+08:00Java Date Time API<p>以往處理日期是用 java.util.Date,以及 java.util.Calendar,但 Date 裡面不存在時區概念,只是 Timestamp 的一個 wrapper,Calendar 也很奇怪的將一月份的數值設定為 0,Java 8 以後可以改用 java.time.* API</p>
<p>java.time 這個 package 裡面有這些 class</p>
<ul>
<li><em>Instant</em> – represents a point in time (timestamp)</li>
<li><em>LocalDate</em> – represents a date (year, month, day)</li>
<li><em>LocalDateTime</em> – same as <em>LocalDate</em>, but includes time with nanosecond precision</li>
<li><em>OffsetDateTime</em> – same as <em>LocalDateTime</em>, but with time zone offset</li>
<li><em>LocalTime</em> – time with nanosecond precision and without date information</li>
<li><em>ZonedDateTime</em> – same as <em>OffsetDateTime</em>, but includes a time zone ID</li>
<li><em>OffsetLocalTime</em> – same as <em>LocalTime</em>, but with time zone offset</li>
<li><em>MonthDay</em> – month and day, without year or time</li>
<li><em>YearMonth</em> – month and year, without day or time</li>
<li><em>Duration</em> – amount of time represented in seconds, minutes and hours. Has nanosecond precision</li>
<li><em>Period</em> – amount of time represented in days, months and years</li>
</ul>
<h2 class="atx" id="有-timezone-的時間">有 Timezone 的時間</h2>
<p>Date 跟 Instant 的概念不同,處理不同時區的做法不同,但 Instant 的做法比較直覺</p>
<pre><code class="fenced-code-block language-java"> Instant nowInstant = Instant.now();
ZoneId zoneIdTaipei = ZoneId.of("Asia/Taipei");
ZoneId zoneIdTokyo = ZoneId.of("Asia/Tokyo");
ZonedDateTime nowZonedDateTimeTaipei = ZonedDateTime.ofInstant(nowInstant, zoneIdTaipei);
ZonedDateTime nowZonedDateTimeTokyo = ZonedDateTime.ofInstant(nowInstant, zoneIdTokyo);
System.out.println("nowZonedDateTimeTaipei="+nowZonedDateTimeTaipei);
System.out.println("nowZonedDateTimeTokyo="+nowZonedDateTimeTokyo);
// 跟想像的一樣,將現在的時間放到不同時區,可得到不同時區的正確時間
// nowZonedDateTimeTaipei=2023-06-14T14:42:44.873547+08:00[Asia/Taipei]
// nowZonedDateTimeTokyo=2023-06-14T15:42:44.873547+09:00[Asia/Tokyo]
Date nowDate = new Date();
System.out.println("nowDate="+nowDate);
TimeZone tz = TimeZone.getTimeZone("Asia/Tokyo");
Calendar calendar = Calendar.getInstance(tz);
calendar.setTime(nowDate);
Date nowDate2 = calendar.getTime();
System.out.println("nowDate2="+nowDate2);
// 因為 Date 沒有時區的概念,是儲存 GMT 1970/1/1 00:00:00 以後所經過的 ms 數值
// 但列印 Date 時,會自動根據執行程式的時區,轉換為該時區的時間
// 把現在時間 Date 放到 Tokyo 時區,取回新的 Date 以後,結果兩個 Date 很奇怪的結果是一樣的
// nowDate=Wed Jun 14 14:42:44 CST 2023
// nowDate2=Wed Jun 14 14:42:44 CST 2023
// 要搭配 SimpleDateFormat
SimpleDateFormat sdfTaipei = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdfTaipei.setTimeZone(TimeZone.getTimeZone("Asia/Taipei"));
SimpleDateFormat sdfTokyo = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdfTokyo.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo"));
System.out.println();
System.out.println("nowDate="+nowDate);
System.out.println("nowDate Taipei="+sdfTaipei.format(nowDate));
System.out.println("nowDate Tokyo="+sdfTokyo.format(nowDate));
// nowDate=Wed Jun 14 14:53:19 CST 2023
// nowDate Taipei=2023-06-14 14:53:19
// nowDate Tokyo=2023-06-14 15:53:19</code></pre>
<h2 class="atx" id="javatime-的優點">java.time 的優點</h2>
<p>immutable 且為 thread-safe,所有的 method 回傳的物件都是產生一個新的物件,物件本身的狀態是永久不變的,因此為 thread-safe。java.util.Date 並不是 thread-safe</p>
<p>因為 method 會回傳新的物件,因此可以做 method chaining</p>
<pre><code class="fenced-code-block language-java">ZonedDateTime nextFriday = LocalDateTime.now()
.plusHours(1)
.with(TemporalAdjusters.next(DayOfWeek.FRIDAY))
.atZone(ZoneId.of("Asia/Taipei"));
System.out.println("plus 1hr nextFriday="+nextFriday);</code></pre>
<h2 class="atx" id="新舊寫法">新舊寫法</h2>
<p>以下是一些特定工作,新舊 API 的不同寫法</p>
<pre><code class="fenced-code-block language-java"> /* get current timestamp */
Date now = new Date();
Instant instant = Instant.now();
// 帶有時區的現在時間
ZonedDateTime zonedDateTime = ZonedDateTime.now();
/* 特定時間 */
Date sDate = new GregorianCalendar(1990, Calendar.JANUARY, 15).getTime();
// New
LocalDate sLocalDate = LocalDate.of(1990, Month.JANUARY, 15);
/* 時間的加減 */
GregorianCalendar calendar = new GregorianCalendar();
calendar.add(Calendar.HOUR_OF_DAY, -5);
Date fiveHoursBeforeOld = calendar.getTime();
// New
LocalDateTime fiveHoursBeforeNew = LocalDateTime.now().minusHours(5);
/* 修改特定欄位 */
// Old
GregorianCalendar calc = new GregorianCalendar();
calc.set(Calendar.MONTH, Calendar.JUNE);
Date inJuneOld = calc.getTime();
// New
LocalDateTime inJuneNew = LocalDateTime.now().withMonth(Month.JUNE.getValue());
/* Date Time truncating */
// Old
Calendar nowCalc = Calendar.getInstance();
nowCalc.set(Calendar.MINUTE, 0);
nowCalc.set(Calendar.SECOND, 0);
nowCalc.set(Calendar.MILLISECOND, 0);
Date truncatedOld = nowCalc.getTime();
// New
LocalTime truncatedNew = LocalTime.now().truncatedTo(ChronoUnit.HOURS);
/* TimeZone 轉換 */
// Old
GregorianCalendar cal2 = new GregorianCalendar();
cal2.setTimeZone(TimeZone.getTimeZone("Asia/Taipei"));
Date centralEasternOld = calendar.getTime();
// New
ZonedDateTime centralEasternNew = LocalDateTime.now().atZone(ZoneId.of("Asia/Taipei"));
/* Time difference */
// Old
GregorianCalendar calc3 = new GregorianCalendar();
Date nowdate = new Date();
calc3.add(Calendar.HOUR, 1);
Date hourLater = calc3.getTime();
long elapsed = hourLater.getTime() - nowdate.getTime();
// New
LocalDateTime nowLocalDateTime = LocalDateTime.now();
LocalDateTime hourLaterLocalDateTime = LocalDateTime.now().plusHours(1);
Duration span = Duration.between(nowLocalDateTime, hourLaterLocalDateTime);
/* Date formatter, parsing */
// Old
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date nowdate2 = new Date();
String formattedDateOld = dateFormat.format(nowdate2);
try {
Date parsedDateOld = dateFormat.parse(formattedDateOld);
} catch (ParseException e) {
throw new RuntimeException(e);
}
// New
LocalDate nowLocalDate = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String formattedDateNew = nowLocalDate.format(formatter);
LocalDate parsedDateNew = LocalDate.parse(formattedDateNew, formatter);</code></pre>
<p>新舊 classes 之間能夠互相轉換</p>
<pre><code class="fenced-code-block language-java"> // GregorianCalendar 轉為 Instant
Instant instantFromCalendar = GregorianCalendar.getInstance().toInstant();
// GregorianCalendar 轉為 ZonedDateTime
ZonedDateTime zonedDateTimeFromCalendar = new GregorianCalendar().toZonedDateTime();
// Instant 轉為 Date
Date dateFromInstant = Date.from(Instant.now());
// ZonedDateTime 轉為 GregorianCalendar
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
// Date 轉為 Instant
Instant instantFromDate = new Date().toInstant();
// TimeZone 轉為 ZoneId
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone("Asia/Taipei").toZoneId();</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.baeldung.com/java-set-date-time-zone">Set the Time Zone of a Date in Java | Baeldung</a></p>
<p><a href="https://www.baeldung.com/java-date-to-localdate-and-localdatetime">Convert Date to LocalDate or LocalDateTime and Back | Baeldung</a></p>
<p><a href="https://www.baeldung.com/migrating-to-java-8-date-time-api">Migrating to the New Java 8 Date Time API | Baeldung</a></p>
<p><a href="https://stackoverflow.com/questions/2891361/how-to-set-time-zone-of-a-java-util-date">How to set time zone of a java.util.Date? - Stack Overflow</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-38097386067173455922024-02-05T08:31:00.003+08:002024-02-05T08:31:56.051+08:00在網頁使用 sqlite<p><a href="https://sql.js.org/#/?id=sqlite-compiled-to-javascript">SQLite compiled to JavaScript</a> 透過 WASM,可在網頁直接載入 sqlite db,使用 SQL 指令操作資料庫。WebAssembly或稱wasm是一個低階程式語言,讓開發者能運用自己熟悉的程式語言(最初以C/C++作為實作目標)編譯,再藉虛擬機器引擎在瀏覽器內執行。透過 WebAssembly 可以讓一些 C/C++ 開發的函示庫,移動到網頁裡面運作。sql.js 就是用這種方式,讓網頁可以直接使用 sqlite 資料庫。</p>
<p>使用sql.js要先初始化資料庫物件</p>
<p>引用 javascript</p>
<pre><code class="fenced-code-block language-javascript"><script src='https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.9.0/sql-wasm.min.js'></script></code></pre>
<p>接下來有兩種方式初始化資料庫</p>
<p>方法 1: fetch</p>
<pre><code class="fenced-code-block language-javascript"> async function initdb() {
let config = {
locateFile: filename => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.9.0/sql-wasm.wasm`
};
const sqlPromise = initSqlJs(config);
const dataPromise = fetch("csv/townsnote.db").then(res => res.arrayBuffer());
const [SQL, buf] = await Promise.all([sqlPromise, dataPromise])
const sqlitedb = new SQL.Database(new Uint8Array(buf));
window.sqlitedb = sqlitedb;
};
initdb();</code></pre>
<p>方法 2: XMLHttpRequest</p>
<pre><code class="fenced-code-block language-javascript"> let config = {
locateFile: filename => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.9.0/sql-wasm.wasm`
};
initSqlJs(config).then(function(SQL){
const xhr = new XMLHttpRequest();
xhr.open('GET', 'csv/townsnote.db', true);
xhr.responseType = 'arraybuffer';
xhr.onload = e => {
const uInt8Array = new Uint8Array(xhr.response);
const db = new SQL.Database(uInt8Array);
window.sqlitedb = db;
// const contents = db.exec("SELECT * FROM towns");
// contents is now [{columns:['col1','col2',...], values:[[first row], [second row], ...]}]
// console.log("contents=",contents);
};
xhr.send();
});</code></pre>
<p>初始化資料庫後,就可以直接使用資料庫,執行 SQL 查詢指令</p>
<pre><code class="fenced-code-block language-javascript">let contents = window.sqlitedb.exec("SELECT * FROM towns where id="+id);</code></pre>
<p>以下是載入 sqlite db,執行一個 SQL 查詢的範例</p>
<pre><code class="fenced-code-block language-html"><!DOCTYPE html>
<html lang="zh-tw">
<head>
<meta charset="utf-8">
<title>test</title>
<script src='https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.9.0/sql-wasm.min.js'></script>
<script>
async function initdb() {
let config = {
locateFile: filename => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.9.0/sql-wasm.wasm`
};
const sqlPromise = initSqlJs(config);
const dataPromise = fetch("csv/townsnote.db").then(res => res.arrayBuffer());
const [SQL, buf] = await Promise.all([sqlPromise, dataPromise])
const sqlitedb = new SQL.Database(new Uint8Array(buf));
window.sqlitedb = sqlitedb;
};
initdb();
function get_town_by_id() {
if(!window.sqlitedb) return;
let id = document.getElementById('id').value;
let contents = window.sqlitedb.exec("SELECT * FROM towns where id="+id);
console.log("contents=", contents);
var jsonArray = JSON.parse(JSON.stringify(contents))
console.log("jsonArray=", jsonArray);
document.getElementById('result').value = JSON.stringify(contents);
};
</script>
</head>
<body>
<input type="text" value="9007010" id="id"></input>
<button onclick="get_town_by_id();">query</button>
<br/><br/>
<textarea id="result" rows="20" cols="50"></textarea>
</body>
</html></code></pre>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-50017501184257119732024-01-22T08:32:00.001+08:002024-01-22T08:32:16.877+08:00Leaflet 基本使用方法<p>大部分想到地圖 API 直覺都是 Google Map,但在商業用途上,Google Map API 有使用量要收費的問題。目前可以透過 Leaflet 使用 OpenStreepMap,這部分在商業用途是可以免費使用的。</p>
<h2 class="atx" id="建立地圖">建立地圖</h2>
<p>使用 leaflet 要先 include css 及 js,一開始要先指定地圖的中心點,以下範例定位在台中市政府。過程是用 <code>L.map</code> 建立地圖物件,然後加入 OpenStreetMap 這個 tile layer</p>
<p>建立地圖的範例</p>
<pre><code class="fenced-code-block language-html"><html>
<head>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<style>
#map { height: 450px; };
</style>
</head>
<body>
<div id="map"></div>
</body>
<script>
var map = null;
function create_map() {
let zoom = 16; // 0 - 18
let taichung_cityhall = [24.1635657,120.6486657]; // 中心點座標
let maptemp = L.map('map',{renderer: L.canvas()}).setView(taichung_cityhall, zoom);
map = maptemp;
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap', // 商用時必須要有版權出處
zoomControl: true, // 是否顯示 - + 按鈕
zoomAnimation:false,
markerZoomAnimation: false,
fadeAnimation: false,
}).addTo(map);
// 強制用 resize window 將所有 tiles 載入
setTimeout(function () {
window.dispatchEvent(new Event('resize'));
}, 1000);
};
create_map();
</script>
</html></code></pre>
<p>當我們把程式整合到比較複雜的網頁中,會發現地圖的區塊在一開始載入後,會出現灰色的區塊,這一段程式碼是修正這個問題,用意是強制用 js resize window,讓 leaflet 能夠載入所有的地圖區塊。</p>
<pre><code class="fenced-code-block language-javascript">// 強制用 resize window 將所有 tiles 載入
setTimeout(function () {
window.dispatchEvent(new Event('resize'));
}, 1000);</code></pre>
<h2 class="atx" id="建立-marker,放上-tooltip">建立 Marker,放上 Tooltip</h2>
<p>因為要使用 bootstrap icon 測試,要先引用 bootstrap-icons,然後加上兩個 css class: icon1, icon2</p>
<pre><code class="fenced-code-block language-javascript"><link href="
https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.min.css
" rel="stylesheet">
<style>
#map { height: 450px; }
.icon1 {
color: #e41a1c;
}
.icon2 {
color: #377eb8;
}
</style></code></pre>
<p>這邊加上兩個 Marker 地圖標記,分別在台中市政府及火車站。</p>
<p>我們使用 <code>L.divIcon</code> ,這裡要注意,不能直接在 html 裡面寫上 <code>style</code> 調整 icon 顏色,只能指定 className 套用 css class。css class 很簡單,就只是修改 icon 的顏色</p>
<p>過程是用 <code>L.marker</code> 產生 marker,然後 <code>bindTooltip</code>,再將 marker 加入地圖中</p>
<pre><code class="fenced-code-block language-javascript">function test_marker() {
console.log("test_marker")
// ref: https://gis.stackexchange.com/questions/291901/leaflet-divicon-how-to-hide-the-default-square-shadow
// 不要寫 style 要用 className
const icon1 = L.divIcon({
html: '<i class="bi bi-geo-alt-fill"></i>',
iconSize: [20, 20],
className: 'icon1'
});
let taichung_cityhall = [24.1635657,120.6486657];
let taichung_station = [24.136941,120.685056];
let taichung_station2 = [24.1360,120.685056];
let marker1 = L.marker(taichung_cityhall, {
icon: icon1
});
marker1.bindTooltip("test1", {
direction: 'bottom', // right、left、top、bottom、center。default: auto
sticky: false, // true 跟著滑鼠移動。default: false
permanent: false, // 是滑鼠移過才出現,還是一直出現
opacity: 1.0
}).openTooltip();
marker1.addTo(map);
// remove marker
// marker1.remove();
const icon2= L.divIcon({
html: '<i class="bi bi-geo-alt-fill"></i>',
iconSize: [20, 20],
className: 'icon2'
});
let marker2 = L.marker(taichung_station2, {
icon: icon2
});
marker2.bindTooltip("test2", {
direction: 'bottom',
sticky: false,
permanent: false,
opacity: 1.0
}).openTooltip();
marker2.addTo(map);
};
test_marker();</code></pre>
<h2 class="atx" id="layer-group-layer-control">Layer Group, Layer Control</h2>
<p>多個 Marker 可以組合成一個 Layer,放到 Layer Group 裡面。再透過右上角 Layer Control,可將該 layer 的標記切換 on/off</p>
<pre><code class="fenced-code-block language-javascript">function test_polyline() {
console.log("test_polyline");
const icon1 = L.divIcon({
html: '<i class="bi bi-geo-alt-fill"></i>',
iconSize: [20, 20],
className: 'icon1'
});
const layerControl = L.control.layers(null).addTo(map);
var test1_geos = [
[24.136941,120.68],
[24.136941,120.685056],
[24.1360,120.6850]
];
var test1_markers = [];
for (let i = 0; i < test1_geos.length; i++) {
let marker = L.marker([test1_geos[i][0], test1_geos[i][1]], {
icon: icon1
});
marker.bindTooltip("test1", {
direction: 'bottom',
sticky: false,
permanent: false,
opacity: 1.0
}).openTooltip();
test1_markers.push(marker);
}
// marker 座標的連線
var polyline1 = L.polyline(test1_geos, {color: '#e41a1c'});
var test1LayerGroup = L.layerGroup(test1_markers).addLayer(polyline1);
map.addLayer(test1LayerGroup);
layerControl.addOverlay(test1LayerGroup, "test1");
map.panTo(test1_geos[1]);
};
test_polyline();</code></pre>
<div class="separator" style="clear: both;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFxidd_3HgbqjFxuCuBDaHgVdlgXtXXAM6c97TzGcG2KKAv5RDGrFYkrFVUQXkvlVkPlJGA5Jw87B14I_YlvWVCwHbqctrIt7PnozJ5Se1qm2cN-bxbxCHh4HWZ6OMZhjUUkBIPHNqSULmrP__ApiYVIY5i2Rvz7VQhGewJ-itMKRAtWqeTDFlQw/s631/LeafletBasic_1.png" style="display: block; padding: 1em 0; text-align: center; "><img alt="" border="0" width="400" data-original-height="367" data-original-width="631" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhFxidd_3HgbqjFxuCuBDaHgVdlgXtXXAM6c97TzGcG2KKAv5RDGrFYkrFVUQXkvlVkPlJGA5Jw87B14I_YlvWVCwHbqctrIt7PnozJ5Se1qm2cN-bxbxCHh4HWZ6OMZhjUUkBIPHNqSULmrP__ApiYVIY5i2Rvz7VQhGewJ-itMKRAtWqeTDFlQw/s400/LeafletBasic_1.png"/></a></div>
<h2 class="atx" id="搭配-vue3-使用的問題">搭配 vue3 使用的問題</h2>
<p>ref: <a href="https://www.cnblogs.com/hjyjack9563-bk/p/16856014.html">https://www.cnblogs.com/hjyjack9563-bk/p/16856014.html</a></p>
<p>在測試過程中,常會在 console 發生類似這樣的錯誤</p>
<pre><code class="fenced-code-block">Cannot read properties of null (reading '_latLngToNewLayerPoint')</code></pre>
<p>這是因為 vue3 搭配 leaflet 才會遇到的問題,解決方式是在使用到 map 時,都要用 <code>toRaw()</code> 轉回原本的物件,所有用到 addlayer,removeLayer,clearLayers的方法 都應該用 toRaw(this.map)</p>
<p>ex:</p>
<pre><code class="fenced-code-block language-javascript">// add layer control
this.layerControl = L.control.layers(null).addTo(toRaw(this.map));</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.letswrite.tw/leaflet-osm-basic/">OSM + Leaflet 學習筆記 1:建地圖、marker、事件、換圖層 - Front-End - Let's Write</a></p>
<p><a href="https://leafletjs.com/examples/quick-start/">Quick Start Guide - Leaflet - a JavaScript library for interactive maps</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-41327439699720262372024-01-15T08:39:00.004+08:002024-01-15T08:39:52.920+08:00webcam 即時影像<p>有一種特殊的 MIME 類型,稱為 multipart/x-mixed-replace,這是由 Netscape 在1995年引入的。當伺服器想向客戶端持續發送資料時,瀏覽器可以在一個 http 通道中,持續接收這些改變的資料。它可以用於串流傳輸圖像的 webcam。</p>
<p>通常 http protocol 會需要 content-length 這個 header,讓接收資料的一端,明確知道這個 http 封包的資料有多少,但 multipart/x-mixed-replace 刻意在 client 打開一個 http 連線後,不回傳 response 的 content length,反而是直接用 multipart 的格式,持續對 client 發送資料。browser 在收到這些 content 時,可用來持續更新畫面。</p>
<p>最常見的應用是 webcam,在高速公路、氣象局的網頁,有即時影像的區塊,就是用這種方式實作的。</p>
<p>response 的資料可以是這種格式,這是持續發送圖片</p>
<pre><code class="fenced-code-block">HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: image/jpeg
<1.jpg>
--frame
Content-Type: image/jpeg
<2.jpg>
--frame
Content-Type: image/jpeg
<3.jpg></code></pre>
<p>也可以是這種格式</p>
<pre><code class="fenced-code-block">HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: text/html
<html><body>0</body></html>
--frame
Content-Type: text/html
<html><body>1</body></html>
--frame
Content-Type: text/html
<html><body>2</body></html></code></pre>
<p>每個 multipart 區塊,可以定義告知 content-type</p>
<p>在氣象局的即時影像網頁,每一次取得影像,都會在 30s 以後,關閉這個連線,使用者必須要重新點一次,才會再取得一次 webcam 資料。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://segmentfault.com/a/1190000018563132">使用 multipart/x-mixed-replace 实现 http 实时视频流</a></p>
<p><a href="http://albert-oma.blogspot.com/2013/11/blog-post_15.html">Albert 的筆記本: 高速公路路況即時影像的作法</a></p>
<p><a href="https://ithelp.ithome.com.tw/m/articles/10298661">iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天</a></p>
<p><a href="https://zh.wikipedia.org/zh-tw/%E6%8E%A8%E9%80%81%E6%8A%80%E6%9C%AF">推播技術 - 維基百科,自由的百科全書</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-60944365038359437342024-01-08T08:45:00.002+08:002024-01-08T08:45:28.787+08:00HSTS(HTTP Strict Transport Security)<p>RFC 6797 規定了網際網路瀏覽安全機制 HSTS(HTTP Strict Transport Security) ,宣告瀏覽器與伺服器之間通訊方式必須強制採用 TLS/SSL。</p>
<p>只要從伺服器端送出一個 Strict-Transport-Security 標頭 (Header) 給瀏覽器,即可告知瀏覽器於未來的某段時間內一律使用 SSL 來和該網站連接。一旦發生憑證失效情況,使用者將無法再瀏覽該網站,如此一來便可大幅減少中間人攻擊的問題發生。</p>
<p>換句話說:HSTS Header 就是負責將 http 強制轉為 https。</p>
<p>設定方式是 Server 回傳的 html response,必須要有 Strict-Transport-Security header,內容為</p>
<pre><code class="fenced-code-block">Strict-Transport-Security: max-age=<expire-time>
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
Strict-Transport-Security: max-age=<expire-time>; preload</code></pre>
<p>max-age 是讓 browser 儲存該設定多久的時間,以秒為單位,通常是設定 31536000 (1年) 以上</p>
<p>includeSubDomains 是套用到所有子網域</p>
<p>preload 不是標準的規格的設定值,是 Preloading Strict Transport Security 的意思,不一定要寫</p>
<p>所以設定值為</p>
<pre><code class="fenced-code-block">Strict-Transport-Security: max-age=31536000; includeSubDomains</code></pre>
<p>維持 HSTS 設定一年,且包含子網域</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://coolmandiary.blogspot.com/2021/06/missing-hsts-header.html">經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header</a></p>
<p><a href="https://ithelp.ithome.com.tw/articles/10248473">HSTS設定 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-80010617620312097012023-12-25T08:51:00.003+08:002023-12-25T08:51:12.824+08:00Daemon Thread in java<p>Java 的 thread 有分為 User 與 Daemon Thread 兩種。</p>
<p>User Thread 的執行優先順序比較高,JVM會等所有的 User Thread 都結束工作後,才會完全地停止工作。</p>
<p>Daemon Thead 的 priority 比較低,通常是用來提供 service 給 user thread 使用。如果有資料 IO 的工作,不建議在 daemon thead 裡面實作。因為JVM 不需要等待 daemon thread,所以可以隨時中止 daemon thread,通常在 daemon thread 會用無窮迴圈實作,如果有 finally 部分的程式,在 daemon thread 並不會保證一定會被執行。</p>
<p>Daemon thead 可用在 garbage collection,釋放記憶體,釋放 cache 的工作。</p>
<p>Daemon thead 要在產生物件後,start 前,用 setDaemon 設定為 daemon thread</p>
<pre><code class="fenced-code-block language-java">NewThread daemonThread = new NewThread();
daemonThread.setDaemon(true);
daemonThread.start();
// 用 isDaemon() 檢查是否為 Daemon Thread
daemonThread.isDaemon();</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.baeldung.com/java-daemon-thread">Daemon Threads in Java | Baeldung</a></p>
<p><a href="https://www.cnblogs.com/luochengor/archive/2011/08/11/2134818.html">JAVA并发编程——守护线程(Daemon Thread) - Luochengor - 博客园</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-91877841741511438482023-12-18T08:54:00.000+08:002023-12-18T08:54:12.536+08:00Fluent API<p>fluent API 是一種 OO API design 方式,可讓 API 的使用者,透過 chain method 的方式持續呼叫該物件的 method。在實作這種 API 介面時,method 必須要在最後回傳物件本身的 reference,以最常見的 set methods 來說,以往都是回傳 void,但要改為回傳 this。</p>
<p>在實作支援 Fluent API 這樣的類別時,要注意物件的 immutability 特性,如果該類別是修改物件內部資料的狀態,那麼就直接回傳 this 就好了,但也有可能是持續產生相同類別的新物件。</p>
<p><a href="https://www.baeldung.com/java-fluent-interface-vs-builder-pattern">Difference Between Fluent Interface and Builder Pattern in Java | Baeldung</a> 這個網頁提出了兩種不同的例子</p>
<p>以下是 User 的 Builder,可透過 set method 不斷地修改使用者的各個欄位資料,一直到最後,確認要產生 User 時,就呼叫 build</p>
<pre><code class="fenced-code-block language-java">public static class Builder {
private String firstName;
private String lastName;
private String email;
private String username;
private Long id;
public Builder firstName(String firstName) {
this.firstName = firstName;
return this;
}
public Builder lastName(String lastName) {
this.lastName = lastName;
return this;
}
// other methods
public User build() {
return new User(firstName, lastName, email, username, id);
}
}</code></pre>
<p>在使用時</p>
<pre><code class="fenced-code-block language-java">Builder userBuilder = new Builder();
User user = userBuilder
.firstName("John")
.lastName("Will")
.build();</code></pre>
<p>另一個要持續產生新的物件的例子是 Html,因為 html 裡面的 String content 是不能直接修改的,故必須要在 method 最後面都產生一個新的物件</p>
<pre><code class="fenced-code-block language-java">public class HtmlDocument {
private final String content;
public HtmlDocument() {
this("");
}
public HtmlDocument(String html) {
this.content = html;
}
public String html() {
return format("<html>%s</html>", content);
}
public HtmlDocument header(String header) {
return new HtmlDocument(format("%s <h1>%s</h1>", content, header));
}
public HtmlDocument paragraph(String paragraph) {
return new HtmlDocument(format("%s <p>%s</p>", content, paragraph));
}
}</code></pre>
<p>使用時</p>
<pre><code class="fenced-code-block language-java">HtmlDocument document = new HtmlDocument()
.header("header")
.paragraph("paragraph 1")
.paragraph("paragraph 2");
String html = document.html();</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://blog.sigplan.org/2021/03/02/fluent-api-practice-and-theory/">Fluent API: Practice and Theory | SIGPLAN Blog</a></p>
<p><a href="https://riptutorial.com/java/example/18901/fluent-programming-style">Fluent programming style</a></p>
<p><a href="http://blog.midoll.top/archives/fluentapi-liu-chang-api-ji-yu-java-jie-shao-">Fluent API — 流畅API(基于Java介绍) | 桃子爱吃桃子</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-31442840705663090962023-12-04T08:42:00.006+08:002023-12-04T08:42:57.591+08:00TSN (Time-Sensitive Networking)<p>時效性網路(Time-sensitive Networking, TSN)是電機電子工程師協會(IEEE)定義的專用於使乙太網路更具確定性的一種網路拓展。TSN 是區域網路(LAN)解決方案,只有在TSN LAN內部才能保證其即時性。</p>
<p>為解決乙太網路低延遲與時間同步的問題,Industrial Ethernet 的 Protocol</p>
<ul>
<li>EtherCAT</li>
<li>EtherNet/IP</li>
<li>PROFINET</li>
<li>Powerlink</li>
<li>Modbus-TCP</li>
<li>SERCOS Ⅲ</li>
</ul>
<p>在應用上來說Industrial Ethernet是與Standard Ethernet不相容的。TSN 解決了這些問題</p>
<ol>
<li> TSN相容於Standards Ethernet IEEE802.1規範,可以與非TSN乙太網路一起使用的區域網路,TSN支援更高頻寬的傳輸速度 (Gbit/s以上)</li>
<li> TSN工作在Layer 2 technology of OSI model</li>
<li> 在Standards Ethernet的區域網路中可以做到:<br> 高頻寬、低延遲、保障頻寬(Priority)、時間同步等功能</li>
<li>透過IEEE 802.1AS (協議簡稱精確時鐘協議Precision Timing Protocol - PTP) 實現TSN裝置之間共享時間戳記 (Time Stamping) 的設備。</li>
</ol>
<p>TSN 並未取代 Layer 2 以上層級的協定,也未定義軟體介面或硬體配置與特點,因此可相容於多種應用程式開發介面 (API)</p>
<p>TSN主要功能是時間同步 (Time Sync)、優先權 (Priority)、可靠性 (Reliability)、資源管理 (Resource Management)。時間同步 (Time Sync) 是透過802.1AS標準,在傳送跟接收的封包上加上時間戳記 (Time Stamping),在區域網路之中可以將設備之間的訊號同步在微秒 (us) 範圍</p>
<p>優先權 (Priority) 是透過802.1Qbu & 802.1Qbv標準,允許將正在傳輸的資料中斷讓優先等級較高的資料進行傳送,等優先等級較高的資料傳送完成後再回到先前被中斷的資料繼續傳輸,確保優先等級較高的資料有最大的傳輸頻寬跟最低的傳輸延遲時間。</p>
<p>可靠性 (Reliability) 是透過802.1CB標準,將原本要傳送的封包複製成多個不同封包,每一個不同的封包會透過不同的路徑來做傳送,最後在接收端會自動消除其它的冗餘 (Redundancy) 封包,使其接收端只會收到一筆封包資料,即使在傳輸路經之中出現了單點的故障情況 (如設備損壞或是電纜線斷開等),都可以確保目的端可以接收到正確且完整的資料。</p>
<p>資源管理(Resource Management) 是透過802.1Qcc標準,將TSN配置分成三種模式:</p>
<ol>
<li>完全分散模式(Fully Distributed Model)</li>
<li>完全集中模式(Fully Centralized Model)</li>
<li>集中&分散混合模式(Centralized & Distributed Model)</li>
</ol>
<p>一些關鍵的 IEEE 802.1 TSN 子標準包括:</p>
<ul>
<li>IEEE 802.1 AS – 時序與同步</li>
<li>IEEE 802.1Qbv – 時間感知塑形器</li>
<li>IEEE 802.3Qbr – 散佈快速流量</li>
<li>IEEE 802.1Qbu – 訊框搶佔</li>
<li>IEEE 802.1Qca – 路徑控制與保留</li>
<li>IEEE 802.1CB – 備援</li>
<li>IEEE 802.1 Qcc – 串流保留的增強與改善</li>
<li>IEEE 802.1 Qch – 迴圈佇列與轉送</li>
<li>IEEE 802.1Qci – 逐一串流過濾與監管</li>
<li>IEEE 802.1CM – 前傳網路的時效性網路</li>
</ul>
<h2 class="atx" id="tsn的應用">TSN的應用</h2>
<h3 class="atx" id="影音設備">影音設備</h3>
<p>電影院、音樂廳控制系統,聲音從表演者經過傳輸到聽眾接收,中間如果有延遲,是很難被接受的</p>
<h3 class="atx" id="汽車控制">汽車控制</h3>
<p>汽車控制系統裡面有四個主流的匯流排構成 LIN、CAN、FlexRay & MOST,不同系統採用不同的匯流排且涉及到相當複雜的佈線,汽車上的設計以及感知器會日趨複雜,連接的設備也愈來愈多,TSN的技術可以區分時間敏感度以及優先層級的資料,降低延遲以及時間同步等優點,系統就能輕易且正確傳送從感測器到到影音串流各種不同類型的資料,進而提供可靠性以及可預測性的高速網路系統。</p>
<h3 class="atx" id="工業應用">工業應用</h3>
<p>感測器連接的節點愈多,想要每個感測器跟控制器的無縫連接就愈困難,工業自動化中,精準的時間是關鍵要素,傳統的乙太網路並無法保證網路延遲範圍,透過TSN的網路環境,在傳統乙太網路上加入了即時性的控管,對網路流量進行優先排序,提供保證延遲範圍,以便對時間較敏感的資料可以在正確的時間傳到正確的目標端。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.macnica.com/apac/galaxy/en/products-support/technical-articles/time-sensitive-networking/">一讀就懂的TSN (Time-Sensitive Networking) 應用與架構 | Macnica Galaxy</a></p>
<p><a href="https://college.itri.org.tw/Home/InfoData/f6e19f2d-f81c-421c-bc36-ea6409ba0a5d/edb4fd72-7952-4a64-927e-d2b5a1a556ee">時間敏感網路(TSN)中央控制器簡介 - 科技新知 - 產業學習網</a></p>
<p><a href="https://www.moxa.com/tw/articles/5-things-you-should-know-tsn-iiot-applications">## TSN 讓工業物聯網和工業 4.0 發揮更大效用 — 您不可不知的 5 件事</a></p>
<p><a href="https://www.allion.com.tw/tsn-avnu/">TSN技術說明與Avnu認證流程 | 百佳泰 Allion Labs</a></p>
<p><a href="https://www.digikey.tw/zh/articles/how-to-implement-time-sensitive-networking-to-ensure-deterministic-communication"># 如何實作時效性網路以確保確定性通訊</a></p>
<p><a href="https://www.2cm.com.tw/2cm/zh-tw/tech/B21085DE9EEB46BBABE022AB9476A5BB">保障時遲性/高傳輸速率 時效性網路掀工業自動化革命 | 新通訊</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-60019482937400599422023-11-27T08:44:00.003+08:002023-11-27T08:44:54.166+08:00Doppler Radar<p><a href="https://zh.wikipedia.org/zh-tw/%E5%A4%9A%E6%99%AE%E5%8B%92%E6%95%88%E5%BA%94">都卜勒效應</a> 波源和觀察者有相對運動時,觀察者接受到波的頻率與波源發出的頻率並不相同的現象。這一現象最初由奧地利物理學家都卜勒於1842年發現。</p>
<p>物體的相對運動會引起頻率的增大或減小。當物體和波源相背離時時,波長會增大,頻率會降低,稱為都卜勒紅移,當物體和波源相向運動時,波長減小,頻率增大,稱為都卜勒藍移,根據探測到的都卜勒頻移 f′ 可以計算出物體的速度。</p>
<p>在馬路上,最常遇到的是消防車與救護車的警報,我們通常會發現,當車子往我們的方向靠近跟遠離時,我們聽到的警報聲音是不一樣的,而且一直在改變。</p>
<p>都卜勒雷達 Doppler radar 是利用都卜勒效應測量物體在雷達波束方向上的路徑運動速度的一種雷達。常用於氣象觀測。</p>
<p>為了檢查心臟、血管的運動狀態,了解血液流動速度,可以通過發射超聲來實現。超聲振盪器產生一種高頻的等幅超聲信號,激勵發射換能器探頭,產生連續不斷的超音波,向人體心血管器官發射,當超音波束遇到運動的臟器和血管時,便產生都卜勒效應,就可以根據反射波與發射的頻率差異求出血流速度,根據反射波以頻率是增大還是減小判定血流方向。</p>
<p>交通警察取締超速行車所使用的雷達槍也是都卜勒雷達的一種。交通警察向行進中的車輛發射頻率已知的超音波同時測量反射波的頻率,根據反射波的頻率變化的多少就能知道車輛的速度。裝有都卜勒測速儀的監視器有時就裝在路的上方,在測速的同時把車輛牌號拍攝下來,並把測得的速度自動列印在照片上。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://zh.wikipedia.org/zh-tw/%E9%83%BD%E5%8D%9C%E5%8B%92%E9%9B%B7%E9%81%94">都卜勒雷達 - 維基百科,自由的百科全書</a></p>
<p><a href="https://smallcollation.blogspot.com/2013/11/doppler-ultrasound.html#gsc.tab=0">杜卜勒超音波(Doppler ultrasound) - 小小整理網站 Smallcollation</a></p>
<p><a href="https://academic-accelerator.com/encyclopedia/zh/doppler-effect">多普勒效應 Doppler Effect</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-19906113671168301662023-11-20T08:29:00.004+08:002023-11-20T08:29:38.121+08:00抽象洩漏定律 (The Law of Leaky Abstractions)<p>Joel Spolsky 於 2002 年提出 Leaky Abstractions 抽象洩漏定律</p>
<p><strong>All non-trivial abstractions, to some degree, are leaky.</strong> 所有難以理解複雜的抽象機制,在某種程度上,都是有漏洞的。</p>
<p>因為軟體的開發與運作環境複雜,開發人員不可能自造所有的輪子,而必須依靠各種抽象化的機制(大部分是 API 函式庫)進行開發,在隱藏細節的環境下,進行開發。經過一個開發人員的實作與開發後,某個程度下,又多了一層抽象封裝。但這些抽象封裝機制,不可避免都會洩漏出底層的一些問題,洩漏出無法封裝的問題。</p>
<p>在使用者使用抽象化後的介面後,在遇到不可預期的問題時,就必須要去了解底層的細節,才能知道發生的原因,也才能解決問題與除錯。雖然抽象封裝節省了開發的時間,但踩雷與除錯所耗費的時間也不少。因此我們才會在很多 QA 網站中,找到一些其他人的踩雷經驗與技巧。有經驗的開發人員,也會因為這些經驗的累積,避開可能會遇到的問題。</p>
<p>以下是一些抽象洩漏的例子</p>
<ul>
<li><p>TCP 是現今網路的基礎,大部分的網路溝通,都需要利用 TCP 的可靠傳輸協定傳送資料,但不可避免的是 TCP 的流量控制機制本身就是有缺陷的協定,網路無法在一個穩定的流量通道上進行傳送,延遲跟 throughput 的波動對於 TCP 來說,都是正常的現象。但一般來說,沒有經驗的開發人員是無法預知到這些問題。</p>
</li>
<li><p>SQL 查詢語言是關聯式資料庫的查詢語法,但某些 SQL 查詢語法卻是有性能差異的,例如 <code>select * from table</code> 就會比 <code>select column1, column2 from table</code> 速度來得慢。另外因為 AP Server 跟 DB 是分屬不同機器的狀況下,查詢時把整個資料表的所有欄位都取出來,也會造成網路頻寬的耗費而影像整體效能。</p>
</li>
</ul>
<p>當我們遇到了一項新技術,宣稱因為良好的封裝,可以加速開發時。這時候最好是停下來想想看,這樣的封裝是不是真的有帶來實際的效益,還是會因為採用了這樣的抽象化封裝,而帶來一些無法預期的問題。</p>
<p>我們曾經使用過可以在 ios 與 android 同時運作的開發工具,但最終因為封裝後的函式庫本身的限制,無法微調,且函式庫無法跟隨作業系統的更新就馬上更新,最終只能放棄而採用原生的方式開發。但這不代表這種工具是不好的,對於畫面簡單的應用程式來說,使用者種方式開發,確實會帶來一些好處,但要有心理準備,可能會遇到一些根本且無法解決的問題。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/">The Law of Leaky Abstractions – Joel on Software</a></p>
<p><a href="https://baike.baidu.com/item/%E6%8A%BD%E8%B1%A1%E6%B3%84%E6%BC%8F/22983571">抽象泄漏_百度百科</a></p>
<p><a href="https://kknews.cc/zh-tw/code/qx5639g.html">為什麼任何系統都會存在Bug?什麼是抽象漏洞定律? - 每日頭條</a></p>
<p><a href="https://www.jdon.com/46006.html">抽象漏洞定律The Law of Leaky Abstractions</a></p>
<p><a href="https://shzhangji.com/cnblogs/2013/12/17/the-law-of-leaky-abstractions/">抽象泄漏定律 | 张吉的博客</a></p>
<p><a href="https://www.wikiwand.com/zh-mo/%E6%8A%BD%E8%B1%A1%E6%B3%84%E6%BC%8F">抽象泄漏 - Wikiwand</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-91116764915652583992023-11-13T08:30:00.002+08:002023-11-13T08:30:09.186+08:00複雜性守恆定律 Tesler's Law<p>Larry Tesler 於 1984 年提出複雜性守恆定律 Tesler's Law。每個程式都有其內在無法簡化的複雜程度,無法被刪除或是隱藏,到了臨界點,就無法再被簡化,因此,必須在人機介面的設計中,不斷地調整與平衡,適當地將使用介面跟產品內部的複雜度調整與轉移。</p>
<p>例如傳統的電視機,畫面裡面的顯示設定很簡單,複雜的是在電視遙控器,遙控器上有數十個按鈕可以進行設定。新的智慧電視,遙控器的介面非常簡潔,但畫面內的功能與設定卻非常多。使用者跟電視之間的交互作用關係的整體複雜度不變,但透過介面設計的不同,轉移了複雜度的比重關係。</p>
<p>Macbook Pro 在外接的介面孔,逐漸地簡化,導致使用者無法直接使用 HDMI、SD card、USB,機器因為孔位減少,外觀簡潔更好看。但使用者卻必須要透過外接的轉接線路,才能連接 USB、SD card。</p>
<p>Tesler's Law 說明了,系統「整體複雜度」是固定的,如果要簡化使用者操作,就會增加其他部分的複雜度,因為產品設計與開發的時間跟人力都需要耗費成本,無法追求極致的使用者體驗。複雜度在平衡與轉移的過程中,都會產生 cost,如果有低成本的轉移方式,就能持續進行轉移。</p>
<p>太過於精簡的介面,也會讓使用者嗤之以鼻,沒有難度的操作方式,會失去眼球焦點,適當的難度可讓使用者持續沈浸於產品的體驗中。更簡單的使用者體驗,通常代表更複雜的系統設計。需要由設計師跟開發團隊,進行複雜度的權衡。</p>
<p>在產品的發展初期,因為核心功能不多,通常介面也會比較簡單,但隨著時間與產品的演進,功能慢慢的擴張,涵蓋到其他的範圍,複雜度也會慢慢地提升。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://zhuanlan.zhihu.com/p/343497540">「自然交互· 泰斯勒定律」如何平衡设计的复杂度?</a></p>
<p><a href="https://baike.baidu.hk/item/%E8%A4%87%E9%9B%9C%E5%BA%A6%E5%AE%88%E6%81%86%E5%AE%9A%E5%BE%8B/7508660">複雜度守恆定律_百度百科</a></p>
<p><a href="https://www.jdon.com/65208.html">什么是特斯勒定律?</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-19603780780061195272023-11-06T08:13:00.003+08:002023-11-06T08:13:47.334+08:00分布式計算的謬論 (The Fallacies of Distributed Computing)<p>Sun MicroSystem 的幾位工程師,提出了分散式系統的八個謬論,是一般開發人員,對於分散式系統的錯誤認知假設,有了這些基本的認知錯誤,設計的系統常會發生一些不能預期的問題。</p>
<p>前四個是 Bill Joy 與 Dave Lyon 定義的,後來 Peter Deutsch 增加了 5,6,7 三個,最後 James Gosling 定義了第八個。</p>
<ol>
<li><p>網路是可靠的</p>
<p>任何透過網路的遠端系統呼叫,都有可能會發生意外而失敗。為了處理遠端系統可能的離線異常,通常會帶入 MQ 系統,將遠端呼叫的需求放入 MQ,然後自動 retry,但加入了 MQ,就會用非同步的方式,處理一開始的 Request,這會直接影響原始的系統設計。</p>
</li>
<li><p>延遲是零</p>
<p>網路因為頻寬的限制,以及客戶端到伺服器端的距離,一定會發生資料傳遞的延遲。</p>
<p>傳統的電話線路是獨佔式的,一條線路只能讓一通電話使用。網路電話是不同的,需要經過語音及類比、數位轉換,然後透過分享的網路線路進行封包傳送,網路電話的延遲通常會比傳統電話還要大。</p>
</li>
<li><p>頻寬是無限的</p>
<p>網路封包是透過無線電或網路線傳送,但實體的傳送媒體,都會因為該傳輸媒體及資料轉換機制,而有資料傳送速度的限制。在處理大資料量的應用,例如網路電話,直播等等,更需要注意頻寬限制的問題。</p>
</li>
<li><p>網路是安全的</p>
<p>網路技術會進步與更新,但網路攻擊的技術也同時在進步,網路攻擊一定存在,也不存在無法被攻擊的系統,系統只能應付攻擊,而做對應的處理。</p>
</li>
<li><p>拓撲結構不會改變</p>
<p>機器與連線的配置與架構會不斷改變,當系統故障/更新時,會需要改變現有的系統架構。</p>
</li>
<li><p>有一個管理員</p>
<p>任何地方都可能會出錯,當系統發生問題,沒有一個管理員可以知道所有的狀況,並能了解所有的問題。</p>
</li>
<li><p>運輸成本為零</p>
<p>因為需要有頻寬、伺服器、網路、Load Balancer、防火牆等等網路架構與機制,所以網路服務都是需要花錢的,沒有能夠免費提供的服務,但現在能使用免費網路服務的原因,都是因為公司用另一個方式,取得了營運資金,用來支撐免費的服務。</p>
</li>
<li><p>網路是同質的</p>
<p>網路是透過各種協定交互運作組成的,如果是開放的標準通訊協定,可在不同系統上交互運作,如果是自訂的通訊規格,就可能會發生無法相容的問題。</p>
</li>
</ol>
<h2 class="atx" id="references">References</h2>
<p><a href="https://juejin.cn/post/7122804182444146701">驾驭分布式计算的8个谬误 - 掘金</a></p>
<p><a href="https://www.readfog.com/a/1683912889199595520"># 圖說分佈式計算的 8 大謬誤</a></p>
<p><a href="https://www.51cto.com/article/716387.html">分布式系统中经典的八个谬误-51CTO.COM</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-21770417048668591752023-10-30T08:34:00.004+08:002023-10-30T08:37:17.222+08:00Vue 的 js 建構版本資訊<h1 class="atx" id="vue-的-js-建構版本資訊">Vue 的 js 建構版本資訊</h1>
<p><a href="https://cdnjs.com/libraries/vue">vue - Libraries - cdnjs</a> vue 有多個版本的 js file,每一個版本有不同的用途</p>
<h2 class="atx" id="cjs">cjs</h2>
<ul>
<li><p>vue.cjs.js</p>
</li>
<li><p>vue.cjs.prod.js 有壓縮的正式版</p>
</li>
</ul>
<p>CommonJS,是一種模組的定義,伺服器端使用,透過 require() 在 NodeJS 裡面使用。但目前 NodeJS 已宣布放棄了 CommonJS</p>
<h2 class="atx" id="global">global</h2>
<ul>
<li><p>vue.global.js 完整版,包含編譯器及 runtime</p>
</li>
<li><p>vue.global.prod.js 正式版</p>
</li>
<li><p>vue.runtime.global.js</p>
</li>
<li><p>vue.runtime.global.prod.js</p>
</li>
</ul>
<p>這是在瀏覽器直接用 <script src""> 引用時使用,會得到一個 global Vue 物件,可直接使用。</p>
<p>完整版跟 runtime 版本的差異是,完整版包含編譯器跟 runtime。編譯器可處理將 template 編譯為 js</p>
<h2 class="atx" id="browser">browser</h2>
<ul>
<li>vue.esm-browser.js</li>
<li>vue.esm-browser.prod.js</li>
<li>vue.runtime.esm-browser.js</li>
<li>vue.runtime.esm-browser.prod.js</li>
</ul>
<p>透過 ES6 原生的 module 使用,可以在瀏覽器內,透過 <code><script type="module"></code> 使用</p>
<h2 class="atx" id="bundler">bundler</h2>
<ul>
<li>vue.esm-bundler.js</li>
<li>bue.runtime.esm-bundler.js</li>
</ul>
<p>用在 webpack, rollup, parcel 等建構工具,通常預設是使用 vue.runtime.esm-bundler.js</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://juejin.cn/post/6970997826457174052">Vue:浅析vue.js完整版 和 vue.runtime.js运行时版 - 掘金</a></p>
<p><a href="https://bbs.huaweicloud.com/blogs/311421">大前端学习笔记--Vue.js 3.0-云社区-华为云</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-65770851075265632402023-10-23T08:37:00.001+08:002023-10-23T08:37:41.234+08:00Chicken-or-Egg Problem in Network<h1 class="atx" id="chicken-or-egg-problem-in-network"></h1>
<p>在網路效應中,常見到 2-sided martketplace,nfx 提出 19 個策略,用來解決先有雞還是先有蛋的問題。</p>
<ol>
<li><p>Get the hardest side first</p>
<p>比較困難的那一邊,也代表其網路價值比較高</p>
<p>ex: Ourdoorsy 是 RV 租借市場,困難點在於如何吸引 RV owners</p>
</li>
<li><p>Appeal tightly to a niche and repeat</p>
<p>找到市場的痛點</p>
<p>ex: eBay 一開始 Beanie Babies。Craigslist 一開始是Craig的朋友想要找公寓,找工作</p>
</li>
<li><p>Subsidize the most valuable side of the market</p>
<p>付費補貼給該網路中最有價值的一方,請他們加入網路</p>
<p>ex: Uber 付費給司機</p>
</li>
<li><p>Make the supply look bigger with automation</p>
<p>從 supply-side 收集資料,進而讓該活動被注意(產生光環)</p>
<p>ex: Yelp 在平台收集評論資料</p>
</li>
<li><p>Buid one side as an email list</p>
<p>這是一個啟動 martketplace 最簡單的方法,特別是 buyer 同時也是 sellers 的狀況</p>
<p>ex: Craigslist</p>
</li>
<li><p>Host meetups & gatherings 辦聚會</p>
<p>舉辦社交活動,可得到客戶直接的反饋</p>
<p>ex: Poshmark 舉辦 "Posh Parties" 讓客人交換時尚資訊</p>
</li>
<li><p>Buid a SaaS tool for one side of the market</p>
<p>為某一方建立 SaaS 工具,這樣就能吸引另一方</p>
<p>ex: OpenTable 為餐廳建立預訂軟體服務</p>
</li>
<li><p>Give software to a thrid party who bring you one side of the market</p>
<p>提供軟體給第三方</p>
<p>ex: Android 為手機製造商提供軟體,手機可帶來消費者需求</p>
</li>
<li><p>Find one giant user for the initial supply or demand</p>
<p>一開始先找一個大客戶</p>
<p>ex: Candex 提供軟體給 Siemens 使用</p>
</li>
<li><p>Only make one side to change their behavior</p>
<p>讓某一方改變行為</p>
<p>ex: Square 改變店家行為,最終讓消費者也使用他們的信用卡</p>
</li>
<li><p>Make something free suddenly</p>
<p>把原本要付費的東西,突然變成免費,吸引客戶</p>
<p>ex: Robinhood 免費交易。Skype 讓電話免費。Napster 讓音樂免費。</p>
</li>
<li><p>Buid a product first, then open a marketplace</p>
<p>先做產品,再打開市場</p>
<p>ex: Salesforce 建立線上 CRM 工具</p>
</li>
<li><p>Connect the two sides by hand</p>
<p>先以人工(代工)方式連接兩端</p>
<p>ex: Zappos 早期處理網路訂單,是以人工去買鞋,然後送貨完成</p>
</li>
<li><p>Favor markets where buyers are sellers,too</p>
<p>先做 buyers 也是 sellers 的市場</p>
<p>ex: Poshmark 使用者買衣服,也會賣衣服</p>
</li>
<li><p>Create exclusive access</p>
<p>建立獨佔式存取,該想法可產生病毒式行銷</p>
<p>ex: Gmail, clubhouse</p>
</li>
<li><p>Set a geographic constraint</p>
<p>設定地理限制,在初期先限制在某些地方發布服務</p>
<p>ex: Lyft, Yelp, Craigslist</p>
</li>
<li><p>Set a time constraint</p>
<p>設定時間限制,初期只在某些時間提供服務</p>
<p>ex: Tophatter 只提供在 8-9pm 可作競標</p>
</li>
<li><p>Set a demand constraint</p>
<p>設定需求限制</p>
<p>ex: Groupon 限制店家每天產生一個 groupon</p>
</li>
<li><p>Pay users with tokens</p>
<p>以代幣支付給使用者,為市場創造代幣,提供代幣給使用者</p>
</li>
</ol>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.nfx.com/post/19-marketplace-tactics-for-overcoming-the-chicken-or-egg-problem">19 Tactics to Solve the Chicken-or-Egg Problem and Grow Your Marketplace</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-83889588368480460022023-10-16T08:37:00.001+08:002023-10-16T08:37:49.236+08:00網路效應<p>nfx.com 有一份 <a href="https://www.nfx.com/post/network-effects-bible">The Network Effects Bible</a> 討論網路效應的重要性,每增加一個使用者,會讓這個產品/服務更有價值,也就是大者恆大的局面,在網路時代,所有系統發展的初期,都還是在獲取使用者的眼球的競爭下,只有大量的使用者,才算是拿到了入場門票,才會有下一個維持跟進化的問題。</p>
<h2 class="atx" id="分類">分類</h2>
<p>文中提到 16 種不同的網路效應(該文章有持續在更新),根據網路化的程度由小到大排列</p>
<ol>
<li><a href="https://www.nfx.com/post/network-effects-manual#physical-direct">Physical</a> (e.g. landline telephones)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#protocol-direct">Protocol</a> (e.g. Ethernet)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#personal-utility-direct">Personal Utility</a> (e.g. iMessage, WhatsApp)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#personal-direct">Personal</a> (e.g. Facebook)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#market-networks-direct">Market Network</a> (e.g. HoneyBook, AngelList)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#marketplace-2-sided">Marketplace</a> (e.g. eBay, Craigslist)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#platform-2-sided">Platform</a> (e.g. Windows, iOS, Android)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#asymptotic-marketplace-2-sided">Asymptotic Marketplace</a> (e.g. Uber, Lyft)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#data-network-effects">Data</a> (e.g. Waze, Yelp!)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#tech-performance-network-effects">Tech Performance</a> (e.g. Bittorrent,Skype)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#language-social">Language</a> (e.g. Google, Xerox)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#belief-social">Belief</a> (currencies, religions)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#bandwagon-social">Bandwagon</a> (e.g. Slack, Apple)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#Expertise">Expertise</a> (Figma, Microsoft Excel)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#Tribal%C2%A0(Social)">Tribal</a> (Apple, Harvard, NY Yankees…)</li>
<li><a href="https://www.nfx.com/post/network-effects-manual#Hub-and-Spoke">Hub-and-Spoke</a> (TikTok, Medium, Craigslist)</li>
</ol>
<p>網路就是 node 與 link 的組合,網路大小以 node 的數量計算,但該數量不完全代表價值,因為該網路內的活動頻繁程度,也會影響價值。</p>
<p>網路密度 Density 是另一個網路的價值屬性,密度越高,代表價值越高,也就是 nodes 之間的 link 強度跟數量。</p>
<p>link 是有方向性的,例如 twitter 的知名人士,以單向方式跟 fans 提供資訊,單向交流,也有可能沒有方向,例如 Messenger 的交談一定是雙向的,因此沒有必要定義方向。</p>
<p>nodes 之間的關係有 1-1 (ex: Facebook) 與 1-to-many (ex: Youtube),1-1 的互動是雙向的,1-to-many 的互動是單向的。網路的 nodes 分佈會有 cluster 的特性,是一群一群的。</p>
<h2 class="atx" id="數學模型">數學模型</h2>
<p>有幾個網路效應的法則,試圖以數學模型說明網路效應的價值</p>
<ol>
<li><p>Sarnoff’s Law</p>
<p>早期的電視廣播,其價值直接跟使用者數量成正比。該網路都是從網路中心單向發送連結到使用者</p>
<pre><code class="fenced-code-block">V = N</code></pre>
</li>
<li><p>Metcalfe’s Law</p>
<p>在 Internet 時代,連上網路的電腦都可以用任何方式互相傳送資訊。因此網路的價值每增加一個 node,該 node 都可以跟原本的其他 node 連結,價值應該要是所有的節點數量。</p>
<pre><code class="fenced-code-block">V = N * (N-1) /2 => 約等於 N^2</code></pre>
</li>
<li><p>Reed’s Law</p>
<p>David P. Reed 於 1999 年提出,增加一個 node 的網路價值,除了連結的節點數量以外,還要再乘上該 node 帶來的潛在的 cluster,該 node 還會產生更多 sub-groups/clusters</p>
<pre><code class="fenced-code-block">V = 2^N</code></pre>
</li>
</ol>
<h2 class="atx" id="網路特徵">網路特徵</h2>
<ol>
<li><p>不規則性</p>
<p>nodes 與 cluster 的分佈並不均勻,各節點之間的 link 狀況,也跟真實世界一樣,沒有固定的樣式</p>
</li>
<li><p>身份:真實、假名、匿名</p>
<p>有真實身份的網路,會比假名的網路能更有效地建構網路。但在加密或間諜應用中,反而需要匿名的網路,但卻可能因為匿名,該網路關係更容易被破壞。</p>
</li>
<li><p>不對稱性</p>
<p>有不同的角色組合的網路,且某一邊的使用者取得比其他的困難。這被稱為是 Demand-Side 或是 Supply-Side Marketplace,例如 Uber</p>
</li>
<li><p>同質、異質網路</p>
<p>當所有 nodes 的角色都一樣時,這是同質網路,如果網路內有分不同的角色,則是異質網路,例如網拍網站,有分買家及賣家兩種角色。</p>
</li>
<li><p>漸近 Asymptotic</p>
<p>通常使用者增加,每個使用者產生的價值也在增加,但當網路成長到某個程度後,網路效應的增加就開始減弱。ex: Uber 乘客的等待時間,隨著司機數量增加到某個程度後,等待時間就無法再一直縮短了</p>
</li>
<li><p>同邊 Same-Side</p>
<p>同樣使用 Windows Office 的使用者,因為檔案相容性的關係,會因為使用者增加而讓該檔案更加流通</p>
</li>
<li><p>橫向 Cross-Side</p>
<p>例如 Uber,driver 增加會增加客人,客人增加也會再吸引更多 driver</p>
</li>
<li><p>Indirect</p>
<p>例如 ebay,新賣家的加入,只會讓賣家之間的競爭更激烈,但由於賣家增加,更吸引買家加入,整體來說,還是會讓所有賣家間接受惠。</p>
</li>
<li><p>Negative</p>
<p>網路的負面效應有兩種:Congestion (增加使用量) 及 Pollution (增加規模)</p>
<p>因為更多人使用網路,會讓網路壅塞,速度變慢。</p>
<p>FB, Twitter 會因為更多人使用,而污染自己個人的 News Feed</p>
</li>
</ol>
<h2 class="atx" id="建構與維護">建構與維護</h2>
<ol>
<li><p>Multiplayer vs. Single-Player Mode</p>
<p>單人模式只會線性增長,多人的聯網產品同時具有單人與多人的價值</p>
</li>
<li><p>轉換成本 switching cost</p>
<p>切換到不相容的產品,會花時間、精神與金錢,這會讓使用者傾向於使用同一個廠商的商品,並待在同一個生態體系下</p>
</li>
<li><p>Chicken or Egg Problem (Cold Start Problem)</p>
<p>這是 2-sided martketplace 常見的問題,buyers/sellers 或是 developers/users</p>
<p>當有一邊的使用者增加,才會讓另一邊也加入。有 <a href="https://www.nfx.com/post/19-marketplace-tactics-for-overcoming-the-chicken-or-egg-problem">19 Tactics to Solve the Chicken-or-Egg Problem and Grow Your Marketplace</a> 能解決此問題</p>
</li>
<li><p>Multi-Tenanting</p>
<p>同時加入多個互相競爭的網路 ex: Lyft, Uber,同時加入多個社交平台</p>
<p>越大的網路會越有知名度,越能吸引新客戶</p>
</li>
<li><p>Disintermediation 去中心化</p>
<p>在 marketplace 或 merket network 產品中很常見。一開始透過該網路進行交易,後續就改為不透過網路平台而直接交易。</p>
</li>
<li><p>Retention 留存率</p>
<p>客戶回頭再使用產品的頻率。留存率高的網路效應才高。</p>
</li>
</ol>
<h2 class="atx" id="結語">結語</h2>
<p>經過這些年的社交網路更迭,有很明顯的世代交替狀況,使用者的增加,確實放大了網路的價值,也許大部分的人會因為要跟其他人溝通與交流,而加入某些網路 ,雖然人數增加了,卻也凸顯了沒有一個系統能夠適用於所有人的情況,系統可以瞬間爆紅,也可能會一下子就因為技術替換與世代交替消失。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://36kr.com/p/1722559905793"># 网络效应“圣经”(上):网络效应为什么重要?其运作原理是什么?</a></p>
<p><a href="https://www.36kr.com/p/1722559938561"># 网络效应“圣经”(下):如何建立和维护网络效应?相关概念有哪些?</a></p>
<p><a href="https://xueqiu.com/6001666304/175229114">《NFX:网络效应圣经》笔记</a></p>
<p><a href="https://www.nfx.com/post/network-effects-manual">The Network Effects Manual: 16 Different Network Effects (and counting)</a></p>
<p><a href="https://www.sgpjbg.com/info/29799.html"># 网络效应是什么?网络效应定律简介</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-12064748389363397422023-10-02T08:32:00.005+08:002023-10-02T08:32:53.334+08:00erlang poolboy<p><a href="https://github.com/devinus/poolboy">poolboy: A hunky Erlang worker pool factory</a> 是一個通用用途的 erlang pool library,主要是用在 pool 管理,對於 server 程式來說,最常見的就是連接資料庫的 connection pool。</p>
<p>連接資料庫最簡單的做法,是在程式中要呼叫使用資料庫時,要經過連線與認證,使用完畢後,就馬上關閉該資料庫連線,但對於 server 來說,因為大量的連線建立關閉,消耗了很多運算資源,故通常會以資料庫 connection pool 的方式來處理。</p>
<p>一般的 connection pool,會有初始的連線 pool 數量,就是一開始啟動時,固定會產生並維持幾個連線數量,然後在所有連線都被佔用時,有一個 overflow 的數量,設定超量使用時,一次多產生幾個連線。另外為了確保該網路連線是正常連線的狀態,DB pool 通常會定時發送一個 SQL 指令,以該指令有沒有回應來確認連線狀態。例如 MySQL 可以使用 <code>SELECT CURRENT_TIMESTAMP</code> 這樣的 SQL 指令。</p>
<p>poolboy 就是用 erlang process 來作為一個 connection 的資源,該 process 的 state 裡面,就記錄該資料庫連線,另外有個 supervisor 用來做連線的資源管理。</p>
<h2 class="atx" id="使用方式">使用方式</h2>
<p>poolboy 以 epgsql 為例,說明如何使用 poolboy,要實作兩個部分 worker 跟 supervisor,另外要做 pool 的設定。細節直接看 poolboy 的 github 首頁即可。</p>
<p>poolboy 裡面的 transaction 就是使用該 pool 的最簡單的方式,先 checkout 一個 worker,然後呼叫 worker 裡面的某一個 function,最終使用完成後,呼叫 checkin 釋放 worker。</p>
<pre><code class="fenced-code-block language-erlang">transaction(Pool, Fun, Timeout) ->
Worker = poolboy:checkout(Pool, true, Timeout),
try
Fun(Worker)
after
ok = poolboy:checkin(Pool, Worker)
end.</code></pre>
<p>checkout 有三種方式</p>
<pre><code class="fenced-code-block language-erlang">% 取得可以使用的 worker process,如果沒有,預設會等待 5s
1> Worker = poolboy:checkout(PoolName).
% returns PID in under 5 seconds or full.
% 不等待,直接回傳可使用的 worker
2> Worker = poolboy:checkout(PoolName, false).
% no waiting, either you have an idle worker for me or not.
% 等待 10s
3> Worker = poolboy:checkout(PoolName, true, 10000).
% like the first one but wait 10 secs instead of 5</code></pre>
<h2 class="atx" id="注意事項">注意事項</h2>
<p><a href="https://toutiao.io/posts/mvume/preview">poolboy 的坑(Erlang) - 开发者头条</a></p>
<p>使用 poolboy 時,要注意幾個問題,產生 worker process 時,不能失敗,如果 worker 失敗,會導致整個 gen_server crash,無法啟動 pool。</p>
<p>解決方法是使用 proxy,也就是在建立 worker 時,並不直接產生 db 的連線,而是直接將 worker process 建立起來,後續在使用到該 worker 時,才去檢查 worker 裡面的 conn state。</p>
<p>這邊的問題是在如何做連線維持,因為 worker 把連線建立延後了,這表示使用 worker 以前,不能確認該 worker 擁有的 conn 到底是不是正常的連線,連線也可能因為 idle 太久而被資料庫直接斷線。</p>
<p>連線維護的部分,會需要在 worker 裡面另外寫 timer,定時做 callback,然後定時發送一個 SQL 指令,以該指令有沒有回應來確認連線狀態。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://github.com/interline/epgsql_pool/">GitHub - interline/epgsql_pool: Pooled epgsql connections using Poolboy</a></p>
<p><a href="https://github.com/hiroeorz/eredis_pool/">GitHub - hiroeorz/eredis_pool: eredis_pool is Pool of Redis clients, using eredis and poolboy.</a></p>
<p><a href="https://blog.csdn.net/weixin_38278878/article/details/105471047">erlang进程池-poolboy源码分析_食鱼酱的博客-CSDN博客</a></p>
<p><a href="https://zhuanlan.zhihu.com/p/259283728"># erlang线程池poolboy源码阅读</a></p>
<p><a href="https://www.triggers-world.de/2017/05/erlang-poolboy-notes/">Erlang poolboy notes – Triggers-World</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-59655998211823211382023-09-25T08:47:00.004+08:002023-09-25T08:47:20.731+08:00firewalld<p>在 rockylinux 8 以前,可以不使用firewalld,還是沿用舊的 iptables</p>
<pre><code class="fenced-code-block language-bash"># Stop firewalld
systemctl stop firewalld
# disable firewalld
systemctl disable firewalld
# hide firewalld
systemctl mask firewalld</code></pre>
<p>安裝 iptables</p>
<pre><code class="fenced-code-block language-bash">dnf -y install iptables iptables-services
systemctl enable iptables
systemctl start iptables
systemctl status --no-pager iptables</code></pre>
<p>在 rockylinux 9 以後,firewalld 成為預設的 firewall</p>
<ul>
<li><p>firewalld 支援 network/firewall zones,定義 trust level of network connections</p>
</li>
<li><p>同時支援 IPv4, IPv6</p>
</li>
<li><p>支援由 service/applcation 直接建立 firewall rules</p>
</li>
</ul>
<p>如果有圖形介面,可透過 <code>firewall-config</code> 進行 firewall 設定</p>
<p>啟用 firewalld</p>
<pre><code class="fenced-code-block language-bash">systemctl enable --now firewalld
systemctl restart firewalld
systemctl status --no-pager firewalld</code></pre>
<h2 class="atx" id="基本指令">基本指令</h2>
<p>以下是 firewalld 幾個基本常用的指令</p>
<pre><code class="fenced-code-block language-bash"># 檢查 firewalld 狀態
firewall-cmd --state
# 設定完成後,要重新載入設定,讓設定永久生效
firewall-cmd --reload
# 查閱設定
firewall-cmd --list-all
# 查閱詳細設定
firewall-cmd --list-rich-rules
# 將設定永久儲存
firewall-cmd --runtime-to-permanent
# 在設定過程中,直接增加rule 並永久儲存
firewall-cmd --permanent [the rest of your command]</code></pre>
<h2 class="atx" id="zone">zone</h2>
<p>firewalld 最重要的是加入了 zone 的概念,以下是內建基本的 zones</p>
<table>
<thead>
<tr>
<th>zone</th>
<th>說明</th>
<th>example use</th>
</tr>
</thead>
<tbody><tr>
<td>drop</td>
<td>不回應任何封包,直接拒絕所有外部連線,只允許內部往外傳送的 packets。</td>
<td>drop incoming connections without reply - only outgoing packets are allowed</td>
</tr>
<tr>
<td>block</td>
<td>以 icmp-host-prohibited, icmp6-adm-prohibited 拒絕外部連線</td>
<td>incoming connections are rejected with an icmp-host-prohibited message for IPv4 and icmp6-adm-prohibited for IPv6</td>
</tr>
<tr>
<td>public</td>
<td>允許所有外部連線</td>
<td>all incoming connections are allowed</td>
</tr>
<tr>
<td>external</td>
<td>有使用 IP 偽裝時,用在外部網路</td>
<td>for use on external networks with masquerading enabled</td>
</tr>
<tr>
<td>dmz</td>
<td>給 DMZ 的電腦使用</td>
<td>for computers on your demilitarized zone that are publicly-accessible with limited access to your internal network</td>
</tr>
<tr>
<td>work</td>
<td></td>
<td>for computers in work areas (nope, I don't get this one either)</td>
</tr>
<tr>
<td>home</td>
<td></td>
<td>for use in home areas (nope, I don't get this one either)</td>
</tr>
<tr>
<td>internal</td>
<td>內部網路使用</td>
<td>for your internal network device access</td>
</tr>
<tr>
<td>trusted</td>
<td>允許所有網路連線</td>
<td>all network connections are accepted</td>
</tr>
</tbody></table>
<p>如果滿足以下兩個條件中某一個,該 zone 就會是在 active 狀態</p>
<ol>
<li><p>zone 被綁定到某一個 network interface</p>
</li>
<li><p>zone 被綁定 source IPs 或 network ranges</p>
</li>
</ol>
<p>一般使用者比較會使用 trusted, home, public 這幾個 zone</p>
<p>zone 的相關指令</p>
<pre><code class="fenced-code-block language-bash"># 查詢 default zone
firewall-cmd --get-default-zone
# 查詢 active zones
firewall-cmd --get-active-zones
# 修改 default zone
firewall-cmd --set-default-zone [your-zone]
# 將某個 zone 綁定 network interface
firewall-cmd --zone=[your-zone] --add-interface=[your-network-device]
# 修改 zone 的 network interface
firewall-cmd --zone=[your-zone] --change-interface=[your-network-device]
# 移除 network interface
firewall-cmd --zone=[your-zone] --remove-interface=[your-network-device]
# 新增 zones
firewall-cmd --new-zone=[your-new-zone]
firewall-cmd --get-zones</code></pre>
<h2 class="atx" id="rule">rule</h2>
<p>port</p>
<pre><code class="fenced-code-block language-bash"># 查詢
firewall-cmd --list-ports
# add remove
firewall-cmd --zone=public --add-port=9001/tcp
firewall-cmd --zone=public --add-port=20000-20100/tcp
firewall-cmd --zone=public --remove-port=9001/tcp</code></pre>
<p>service</p>
<pre><code class="fenced-code-block language-bash"># 查詢可使用的 services
firewall-cmd --get-services
# 查詢目前的 active services
firewall-cmd --list-services
# add/remove
firewall-cmd --zone=public --add-service=http
firewall-cmd --zone=public --remove-service=http</code></pre>
<p>ip</p>
<pre><code class="fenced-code-block language-bash">firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
firewall-cmd --permanent --zone=trusted --remove-source=192.168.1.0/24</code></pre>
<p>rich rule</p>
<pre><code class="fenced-code-block language-bash">#單一IP
firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.1.100' reject"
#除了針對特定單一IP外,再針對特別連線port進行設定
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.1.100" port protocol="tcp" port="3306" accept'
#針對IP範圍
firewall-cmd --add-rich-rule="rule family='ipv4' source address='192.168.1.0/24' reject"
firewall-cmd --add-rich-rule='rule family="ipv4" source address="192.168.77.100/32" accept'</code></pre>
<p>在 iptables, firewalld 允許某個 ip 使用 ssh</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p tcp -m tcp -s 192.168.1.122 --dport 22 -j ACCEPT
# firewalld
firewall-cmd --zone=trusted --add-source=192.168.1.122 --permanent
firewall-cmd --zone=trusted --add-service=ssh --permanent
# remove
firewall-cmd --zone=trusted --remove-source=192.168.1.122
firewall-cmd --zone=trusted --remove-service ssh
# 儲存設定
firewall-cmd --runtime-to-permanent
firewall-cmd --reload</code></pre>
<p>ICMP rule</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p icmp -m icmp --icmp-type 8 -s 192.168.1.136 -j ACCEPT
# "public" zone 的 ICMP 預設是開啟的
# block ICMP in "public" "trusted" zone
firewall-cmd --zone=public --add-icmp-block={echo-request,echo-reply} --permanent
firewall-cmd --zone=trusted --add-icmp-block={echo-request,echo-reply} --permanent</code></pre>
<p>web server ports</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
# add service
firewall-cmd --zone=public --add-service=http --add-service=https --permanent
# 移除
firewall-cmd --zone=public --remove-service=http --remove-service=https --permanent</code></pre>
<p>DNS</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p udp -m udp -s 192.168.1.0/24 --dport 53 -j ACCEPT
#
firewall-cmd --zone=trusted --add-service=dns
firewall-cmd --zone=public --add-service=dns</code></pre>
<p>TCP state</p>
<pre><code class="fenced-code-block language-bash">iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# firewalld 不需要做這個設定</code></pre>
<p>mysql</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p tcp -m tcp --dport=3600 -j ACCEPT
# firewalld
firewall-cmd --zone=public --add-service=mysql --permanent</code></pre>
<p>postgresql</p>
<pre><code class="fenced-code-block language-bash"># iptables
iptables -A INPUT -p tcp -m tcp --dport 5432 -s 192.168.1.0/24 -j ACCEPT
# firewalld
firewall-cmd --zone=trusted --add-service=postgresql</code></pre>
<h2 class="atx" id="references">References</h2>
<p><a href="https://docs.rockylinux.org/guides/security/enabling_iptables_firewall/">Enabling <code>iptables</code> Firewall - Documentation</a></p>
<p><a href="https://docs.rockylinux.org/guides/security/firewalld/">firewalld from iptables - Documentation</a></p>
<p><a href="https://docs.rockylinux.org/guides/security/firewalld-beginners/">firewalld for Beginners - Documentation</a></p>
<p><a href="https://docs.ossii.com.tw/books/rocky-linux-8/page/f1b6f">基本防火牆設定 | 晟鑫科技線上手冊</a></p>
<p><a href="https://hackmd.io/@nfu-johnny/rkesuh1HY">Firewalld 防火牆 - HackMD</a></p>
<p><a href="https://hoohoo.top/blog/firewalld-firewall-installation-allow-disable-ip-port-usage-introduction/">Linux Firewall-cmd 防火牆安裝, 允許/禁止 IP, Port 用法介紹</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-60966980683554580532023-09-18T08:39:00.000+08:002023-09-18T08:39:04.508+08:00OAuth<p>傳統的 Client-Server 架構裡, Client 要拿取受保護的資源 (Protected Resoruce) 的時候,要向 Server 出示使用者 (Resource Owner) 的帳號密碼才行。如果要讓第三方應用程式也可以使用這些 Resources ,則需要 Resource Owner 把帳號密碼給這個第三方應用程式,這時候會產生以下的問題:</p>
<ul>
<li>第三方應用程式必須以明碼儲存 Resource Owner 的帳號密碼</li>
<li>Server 要支援密碼認證</li>
<li>第三方應用程式會得到完整存取 Protected Resources 的權限,無法限制時效</li>
<li>Resource Owner 無法只撤回某一個第三方應用程式的存取權,必須要修改密碼才能撤回。</li>
<li>當某一個第三方應用程式被破解,就會導致使用該密碼的所有資料被破解。</li>
</ul>
<p>OAuth 解決這些問題的方式,是引入一個認證層 (authorization layer) ,並把 client 跟 resource owner 的角色分開。Client 會先索取存取權,來存取 Resource Owner 擁有的資源,這些資源會放在 Resource Server 上面,並且 Client 會得到一組不同於 Resource Owner 所持有的認證碼,也就是 access token。</p>
<h2 class="atx" id="角色定義">角色定義</h2>
<ul>
<li><p><strong>Resource Owner</strong></p>
<p>可授權別人去存取 Protected Resource。如果這個角色是人類的話,就是指使用者 (end-user)。</p>
</li>
<li><p><strong>Resource Server</strong></p>
<p>存放 Protected Resource 的伺服器,可以根據 Access Token 來接受使用 Protected Resource 的請求。</p>
</li>
<li><p><strong>Client</strong></p>
<p>讓 Resource Owner 授權後,可以去存取 Protected Resource 的應用程式。</p>
</li>
<li><p><strong>Authorization Server</strong></p>
<p>認證 Resource Owner 並獲得 Resource Owner 許可後,核發 Access Token 的伺服器。</p>
</li>
</ul>
<pre><code class="fenced-code-block">+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+</code></pre>
<p>Refresh Token 流程</p>
<pre><code class="fenced-code-block">+--------+ +---------------+
| |--(A)------- Authorization Grant --------->| |
| | | |
| |<-(B)----------- Access Token -------------| |
| | & Refresh Token | |
| | | |
| | +----------+ | |
| |--(C)---- Access Token ---->| | | |
| | | | | |
| |<-(D)- Protected Resource --| Resource | | Authorization |
| Client | | Server | | Server |
| |--(E)---- Access Token ---->| | | |
| | | | | |
| |<-(F)- Invalid Token Error -| | | |
| | +----------+ | |
| | | |
| |--(G)----------- Refresh Token ----------->| |
| | | |
| |<-(H)----------- Access Token -------------| |
+--------+ & Optional Refresh Token +---------------+</code></pre>
<h2 class="atx" id="grant-flow">Grant Flow</h2>
<h3 class="atx" id="authorization-code-grant-type-flow">Authorization Code Grant Type Flow</h3>
<pre><code class="fenced-code-block">+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI ---->| |
| User- | | Authorization |
| Agent -+----(B)-- User authenticates --->| Server |
| | | |
| -+----(C)-- Authorization Code ---<| |
+-|----|---+ +---------------+
| | ^ v
(A) (C) | |
| | | |
^ v | |
+---------+ | |
| |>---(D)-- Authorization Code ---------' |
| Client | & Redirection URI |
| | |
| |<---(E)----- Access Token -------------------'
+---------+ (w/ Optional Refresh Token)</code></pre>
<ul>
<li>要向 Authorization Server 先取得 Grant Code 再取得 Access Token。</li>
<li>適合 Confidential Clients ,如部署在 Server 上面的應用程式。</li>
<li>可以核發 Refresh Token。</li>
<li>需要 User-Agent Redirection。</li>
</ul>
<h3 class="atx" id="implicit-grant-type-flow">Implicit Grant Type Flow</h3>
<pre><code class="fenced-code-block">+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-- & Redirection URI --->| |
| User- | | Authorization |
| Agent -|----(B)-- User authenticates -->| Server |
| | | |
| |<---(C)--- Redirection URI ----<| |
| | with Access Token +---------------+
| | in Fragment
| | +---------------+
| |----(D)--- Redirection URI ---->| Web-Hosted |
| | without Fragment | Client |
| | | Resource |
| (F) |<---(E)------- Script ---------<| |
| | +---------------+
+-|--------+
| |
(A) (G) Access Token
| |
^ v
+---------+
| |
| Client |
| |
+---------+</code></pre>
<ul>
<li>Authorization Server 直接向 Client 核發 Access Token (一步)。</li>
<li>適合非常特定的 Public Clients ,例如跑在 Browser 裡面的應用程式。</li>
<li>Authorization Server 不必(也無法)驗證 Client 的身份。</li>
<li>禁止核發 Refresh Token。</li>
<li>需要 User-Agent Redirection。</li>
<li>有資料外洩風險。</li>
</ul>
<h3 class="atx" id="resource-owner-password-credentials-grant-type-flow">Resource Owner Password Credentials Grant Type Flow</h3>
<pre><code class="fenced-code-block">+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +---------------+
| |>--(B)---- Resource Owner ------->| |
| | Password Credentials | Authorization |
| Client | | Server |
| |<--(C)---- Access Token ---------<| |
| | (w/ Optional Refresh Token) | |
+---------+ +---------------+</code></pre>
<ul>
<li>Resource Owner 的帳號密碼直接拿來當做 Grant。</li>
<li>適用於 Resource Owner 高度信賴的 Client (像是 OS 內建的)或是官方應用程式。</li>
<li>其他流程不適用時才能用。</li>
<li>可以核發 Refresh Token。</li>
<li>沒有 User-Agent Redirection。</li>
</ul>
<h3 class="atx" id="client-credentials-grant-type-flow">Client Credentials Grant Type Flow</h3>
<pre><code class="fenced-code-block">+---------+ +---------------+
| | | |
| |>--(A)- Client Authentication --->| Authorization |
| Client | | Server |
| |<--(B)---- Access Token ---------<| |
| | | |
+---------+ +---------------+</code></pre>
<ul>
<li>Client 的帳號與密碼直接用來 Grant</li>
<li>適用於跑在 Server 上面的 Confidential Client</li>
<li>不建議核發 Refresh Token</li>
<li>沒有 User-Agent Redirection</li>
</ul>
<h2 class="atx" id="references">References</h2>
<p><a href="https://blog.yorkxin.org/posts/oauth2-1-introduction.html">OAuth 2.0 筆記 (1) 世界觀</a></p>
<p><a href="https://blog.yorkxin.org/posts/oauth2-4-1-auth-code-grant-flow.html">OAuth 2.0 筆記 (4.1) Authorization Code Grant Flow 細節</a></p>
<p><a href="https://blog.yorkxin.org/posts/oauth2-4-2-implicit-grant-flow.html">OAuth 2.0 筆記 (4.2) Implicit Grant Flow 細節</a></p>
<p><a href="https://blog.yorkxin.org/posts/oauth2-4-3-resource-owner-credentials-grant-flow.html">OAuth 2.0 筆記 (4.3) Resource Owner Password Credentials Grant Flow 細節</a></p>
<p><a href="https://blog.yorkxin.org/posts/oauth2-4-4-client-credentials-grant-flow.html">OAuth 2.0 筆記 (4.4) Client Credentials Grant Flow 細節</a></p>
<p><a href="https://blog.yorkxin.org/posts/oauth2-implementation-differences-among-famous-sites.html">各大網站 OAuth 2.0 實作差異 2013</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-79463479027056428762023-09-11T08:36:00.001+08:002023-09-11T08:36:48.592+08:00mDNS DNS-SD<p>DNS-SD(DNS Service Discovery) 跟 mDNS (multicast DNS) 是不同的 protocol,但可以互相相容。DNS-SD 能透過標準的 DNS 技術,尋找區域網路中,提供某些服務的協定。而 mDNS 是能夠在區域網路中,在不需要 DNS Server 的情況下,就能透過機器名稱,直接查出 IP。</p>
<p>apple 的 Bonjour protocol 就是合併了 mDNS 與 DNS-SD 實作的。</p>
<h2 class="atx" id="mdns">mDNS</h2>
<p>可用在沒有 DNS server 的區域網路中,用來作機器 domain name 的 IP 查詢。設備會透過對 224.0.0.251 這個 multicast address,Port 為 5353,進行廣播,mDNS 使用跟 DNS 一樣的封包格式。mDNS 只接受 .local 的網域名稱,可同時運作在一個設備中,跟原本的 DNS 並存。</p>
<p>mDNS 也有為自己命名的功能,在自選了一個 domain name 後,會用記錄類型為 any 的 mDNS 封包查詢是否有同樣的 domain name 的另一台機器,如果沒有,就會設定為自己的 domain name。</p>
<h2 class="atx" id="dns-sd">DNS-SD</h2>
<p>基於 DNS,主要用到三種記錄類型:PTR、SRV、TXT</p>
<ul>
<li><p>Service Discovery</p>
<p>設備會先發送一個 PTR 記錄的 multicast 查詢封包,查詢的格式為</p>
<pre><code class="fenced-code-block"><service>.<transport>.<domain></code></pre>
<p>service 是查詢的服務,transport 為傳輸協定: TCP 或 UDP,domain 為查詢網域,在 mDNS 為 .local。查詢後,具有該服務的設備就會回應</p>
<pre><code class="fenced-code-block"><instance>.<service>.<transport>.<domain></code></pre>
<p>ex: 查詢 <code>_easylink._tcp.local</code>,<code>EMW3031 Module#500A3F._easylink._tcp.local</code> 回應</p>
<p>就表示 <code>EMW3031 Module#500A3F</code> 是符合該 service 的 instance</p>
</li>
<li><p>取得 instance 的 domain name 與 port</p>
<p>當有多個 instance 回應後,選擇某一個 instance,然後需要查詢該 instance 的 domain name 與 port,也就是查詢 SRV 記錄</p>
<pre><code class="fenced-code-block">_service._proto.name. TTL class SRV priority weight port target.</code></pre>
<p>priority 和 weight 沒有作用,通常設定為 0</p>
<p>port 與 target 就分別是 port 與 domain name</p>
<p>ex:</p>
<pre><code class="fenced-code-block">EMW3031 Module#500A3F._easylink._tcp.local. 3 IN SRV 0 0 8002 EMW3031 Module#500A3F.local.</code></pre>
</li>
<li><p>更詳細的資訊</p>
<p>除了 domain name 與 port 以外,還能夠提供更多資訊,就是記錄在 TXT record 中,以 <code>key=value</code> 格式記錄的資訊</p>
<p>ex:</p>
<pre><code class="fenced-code-block">MAC=D0:BA:E4:50:0A:3F</code></pre>
</li>
</ul>
<h2 class="atx" id="centos7-avahi">CentOS7 avahi</h2>
<p>Avahi is a system which facilitates service discovery on a local network via the mDNS/DNS-SD protocol suite.</p>
<p>安裝 avahi</p>
<pre><code class="fenced-code-block language-bash">yum install nss-mdns avahi avahi-tools</code></pre>
<p>啟動</p>
<pre><code class="fenced-code-block language-bash">systemctl start avahi-daemon</code></pre>
<p>出現錯誤訊息</p>
<pre><code class="fenced-code-block"> dbus_bus_request_name(): Connection ":1.44573" is not allowed to own the service "org.freedesktop.Avahi" due to security policies in the configuration file</code></pre>
<p>要重新啟動 dbus, NetworkManager</p>
<pre><code class="fenced-code-block language-bash">systemctl restart dbus.service
# /var/log/messages 會出現錯誤訊息
# (NetworkManager:624): GLib-GIO-CRITICAL **: Error while sending AddMatch () message: 這個連線已關閉
systemctl restart NetworkManager
# 避免 ssh 登入問題
# Failed to activate service 'org.freedesktop.login1': timed out
systemctl restart systemd-logind</code></pre>
<p>再啟動 avahi-demon</p>
<pre><code class="fenced-code-block language-bash">systemctl start avahi-daemon</code></pre>
<p>然後就能查詢 LAN 的機器</p>
<pre><code class="fenced-code-block"># avahi-browse -a
+ enp3s0 IPv4 lzstg [6c:62:6d:ce:71:c7] Workstation local
+ enp3s0 IPv4 macmini2 Microsoft Windows Network local
+ enp3s0 IPv4 macmini2 Apple File Sharing local
+ enp3s0 IPv4 macmini2 Apple Net Assistant local
+ enp3s0 IPv4 Michael's MacBook Pro Microsoft Windows Network local
+ enp3s0 IPv4 Michael's MacBook Pro _companion-link._tcp local</code></pre>
<p>也可以直接 ping *.local 的機器</p>
<pre><code class="fenced-code-block"># ping macmini2.local
PING macmini2.local (192.168.1.159) 56(84) bytes of data.
64 bytes from 192.168.1.159 (192.168.1.159): icmp_seq=1 ttl=64 time=0.675 ms
64 bytes from 192.168.1.159 (192.168.1.159): icmp_seq=2 ttl=64 time=0.374 ms</code></pre>
<p>發布 service</p>
<pre><code class="fenced-code-block">avahi-publish-service SERVICE-NAME _APPLICATIONPROTOCOL._TRANPOSRT-PROTOCOL PORT "DESCRIPTION" --sub SUBPROTOCOL</code></pre>
<p>ex:</p>
<pre><code class="fenced-code-block">avahi-publish-service light _coap._udp 5683 “/mylight” --sub
_floor1._sub._coap._udp</code></pre>
<p>發布 service name: light,使用 CoAP protocol,在 UDP 5683 提供服務</p>
<p>該 service 可透過 <code>_coap._udp.local</code> 及 <code>_floor1._sub._coap._udp.local</code> 被發現</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://dotblogs.com.tw/pou/2017/09/07/024500">UWP - mDNS 找尋附近的設備</a></p>
<p><a href="https://www.win.tue.nl/~johanl/educ/IoT-Course/mDNS-SD%20Tutorial.pdf">MDNS/DNS-SD TUTORIAL</a></p>
<p><a href="https://kknews.cc/zh-tw/code/xeqvnp9.html">使用 mDNS 在區域網中輕鬆發現系統</a></p>
<p><a href="https://blog.csdn.net/ScarletMeCarzy/article/details/106544095">Bonjour手把手搭建一:mDNS(apple & multicastdns.org)</a></p>
<p><a href="https://kknews.cc/zh-tw/tech/o4ybmm.html">區域網設備發現之Bonjour協議</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-86766355823234094242023-08-21T08:29:00.005+08:002023-08-21T08:29:40.342+08:00柯林漢定律 (Kernighan's Law)
<p><a href="https://en.wikipedia.org/wiki/Brian_Kernighan">Brian Kernighan</a> 在其著作 "The Elements of Programming Style" 提出一個經驗法則,稱為 Kernighan's Law。</p>
<pre><code class="fenced-code-block">Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible,
you are, by definition,
not smart enough to debug it.
除錯要比寫程式困難兩倍,
因此,如果程式寫得很精巧,
那你就沒有足夠的智慧能夠除錯。</code></pre>
<p>對這句話的理解,我認為是跟程式的簡潔度及可讀性有關。</p>
<p>越複雜冗長的程式碼,因為程式碼太長,在 debug 時,就越不容易找到問題點。但有些人會在寫程式時,追求到極致,希望能用非常短的程式碼完成。<a href="https://en.wikipedia.org/wiki/One-liner_program">One-liner program</a> 的目標是,用一行程式碼完成一項功能,追求簡潔的程式碼。</p>
<p>這種做法沒有對錯,在高階程式語言,用很短的程式碼完成很多事情,比較容易發生,但在中低階語言,像是 C 語言,刻意追求 one-liner,會產生反效果,因為程式碼太短,而讓人難以理解在寫什麼。</p>
<p>因此良好的程式,應該要具有相當程度的可讀性,可讀性並不代表程式碼很多或很少,而是程式碼以大家都能理解的方式撰寫,程式的結構良好,一個可讀性高的程式,程式碼的長度可長可短。</p>
<p>原文中的精巧,可能是針對 C 語言的說法,因為 C 語言是中低階程式語言,精巧的程式,是聰明的寫法,也代表能用比較短的程式完成工作,在 CPU 及記憶體貧乏的時代,這種做法是有必要的,但這也代表這些程式碼會讓人難以理解,也就更難除錯。</p>
<p>Brian Kernighan 跟 Dennis Ritchie 是 "The C Programming Language" 這本經典書籍的作者,這本書是所有寫 C 語言要閱讀的經典書籍,也因為這本書,建立了一個規則,所有程式語言的第一個測試程式,都是要列印 "Hello, World!" 這個字串到螢幕上。另外這本書的 C 語言 coding style 也被稱為 K & R style。</p>
<p>Kernighan 還是 Unix 作業系統的命名者,原本被稱為 UNICS (Uniplexed Information and Computing System),另外還是 awk 這個工具的作者。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://www.techbang.com/posts/99399-at-the-age-of-80-he-is-still-changing-the-code-he-is-the-name#top">80歲了還在改程式碼的大神:他是Unix命名人、寫下所有程式新手的「Hello World」起手式 | T客邦</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0tag:blogger.com,1999:blog-9289096.post-78717935502023778832023-08-14T08:31:00.004+08:002023-08-14T08:31:38.825+08:00過早優化效應 (Premature Optimization Effect)<p>Donald Knuth 在 The Art of Computer Programming 提出了過早優化法則</p>
<pre><code class="fenced-code-block">We should forget about small efficiencies,
say about 97% of the time:
premature optimization is the root fo all evel.
Yet we should not pass up our opportunities
in that critical 3%.
有 97% 的優化是不值得花時間去做的,過早優化是萬惡根源。
但還是要注意在關鍵的 3% 要提早去優化。</code></pre>
<p>把開發時間花在不重要的優化上,可能會因為過度優化,而化簡為繁,讓系統太過複雜。過早的優化,會浪費大量資源,包含時間、金錢、人力,同時也可能因為這些優化,而衍生出其他的問題。</p>
<p>對於這個法則的爭論點在於,系統的哪個部分是關鍵的 3%。</p>
<p>這個問題跟開發人員的經驗、能力,專案的時程跟預算,系統規劃時設定的最大容量這些有關係。</p>
<p>要知道系統的 3% 關鍵,需要在產品規劃與設計時,就能夠預判系統的使用環境,同時的上線人數。但要能正確預判專案的基本條件並不容易,尤其是一個面對未知系統人數的服務,因為我們永遠不知道,這個系統是不是會爆紅而導致系統的瞬間流量突然增加。一但發生這個狀況,也只能面對,因為一般使用者,甚至是系統的 stake holder 都無法正確預判,有時候就只能等到事情發生了才知道,但也過了那個大流量的使用時機,因為使用者已經失去了信心,不會再用。</p>
<p>有經驗的開發人員,因為經驗的累積,能夠用以往的專案經驗,在新專案一開始的時候,就套用了一個基本能夠彈性修改的系統架構,但難免會遇到一個根本的問題,就是系統的運作硬體環境,程式語言或是 framework 本身的性能瓶頸,或是專案預算的限制。</p>
<p>問題都是存在的,但我們要知道,所有的系統使用時都有極限與限制,盡可能以漸進的方式使用某項新的系統與技術,用保守的態度面對,這樣應該比較能避免遇到問題。激進的方法當然也有可能成功,就看後續的問題處理,運氣好的話,也能撐過動蕩期。</p>
<h2 class="atx" id="references">References</h2>
<p><a href="https://cloud.tencent.com/developer/article/1525574">过早优化是万恶之源——克努特优化原则 (Knuth's optimization principle) - 腾讯云开发者社区-腾讯云</a></p>
<p><a href="https://medium.com/@HyperConnezion/%E6%B5%81%E8%A8%80%E7%B5%82%E7%B5%90%E8%80%85-%E9%81%8E%E6%97%A9%E9%80%B2%E8%A1%8C%E5%84%AA%E5%8C%96%E6%98%AF%E8%90%AC%E6%83%A1%E4%B9%8B%E6%BA%90-98a550df6755"># 流言終結者:過早進行優化是萬惡之源?</a></p>
<p><a href="https://www.zhihu.com/question/24282796"># 「过早的优化是万恶之源」这种说法对不对,为什么?</a></p>
cctghttp://www.blogger.com/profile/05516073474557428585noreply@blogger.com0