2024/07/29

CORS

同源政策 Same-origin policy 是在Web瀏覽器中,允許某個網頁指令碼也就是 javascript 訪問另一個網頁的資料的前提是,這兩個網頁必須有相同的URI、主機名和埠號,一旦兩個網站滿足上述條件,這兩個網站就被認定為具有相同來源。在 same origin policy 的限制下,能限制瀏覽器防止某個網頁上的惡意指令碼通過該頁面的文件物件模型存取另一網頁上的敏感資料。

滿足同源的條件有三個

  1. 使用相同的通訊協定 http/https

  2. 有相同的網域 domain

  3. 有相同的 port

跨來源請求 cross-origin http request,就是在非同源的情況下,發生的 http request,在發生這種狀況時,必須要遵守 CORS (Cross-Origin Resource Sharing) 的規範

定義

CORS 就是針對非同源的 http request 制定的規範,當 javascript 存取非同源的資源時, server 必須明確告知瀏覽器,是否允許這樣的 request,只有 server 允許的 request 能夠被瀏覽器發送,否則就會失敗

CORS 的跨來源請求有兩種:「簡單」與「非簡單」

跨來源請求

「簡單」跨來源請求有兩個條件

  1. 只能使用 GET, POST, HEAD method

  2. http reques header 只能有 AcceptAccept-LanguageContent-Language 或 Content-Type,且 Content-Type 只能是 application/x-www-form-urlencodedmultipart/form-data 或 text/plain

只要不是「簡單」跨來源請求,就是「非簡單」跨來源請求

簡單請求 simple requests 不會觸發 CORS 預檢 preflighted

sample

以下是一個 cross-origin request/response,可發現在 request 裡面,會有一個 Origin http header

Origin 標記,這是來自 http://foo.example 的 cross-orign request

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.example


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[XML Data]

下面的 response 有 Access-Control-Allow-Origin 這個 header,* 表示server 允許來自任意 origin 的 http request

預檢 preflighted request

「非簡單」request,在瀏覽器真正發送 request 之前,會先發送一個 preflight request,用途是詢問 server 是否允許這樣的 request

preflighted request 是用 http OPTIONS method 產生的

在 request 會有這兩個 header

  1. Access-Control-Request-Method:該 request 是哪一個 http method

  2. Access-Control-Request-Headers:該 request 裡面待有哪些非簡單的 http header

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

response 有以下重點

  1. Access-Control-Allow-Origin:允許的 origin

  2. Access-Control-Allow-Methods:允許的跨 origin http method

  3. Access-Control-Allow-Headers:允許使用的 http header

  4. Access-Control-Max-Age:本次預檢請求回應所可以快取的秒數,代表 browser 可 cache 這個 response 多久

cookie

通常 CORS 不允許使用 cookie,也不支援 redirect

如果真的還是要使用 cookie

fetch API 要加上 credentials

fetch('https://othersite.com/data', {
  credentials: 'include'
})

XMLHttpRequest 要加上 withCredentials = true

const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'https://othersite.com/data');

server 端也要增加一個 response header

Access-Control-Allow-Credentials: true

使用 cookie 時,Access-Control-Allow-Origin 不能直接填寫 *

必須明確標示哪些 origin 可以使用 cookie

References

跨來源資源共用(CORS) - HTTP | MDN

[教學] 深入了解 CORS (跨來源資源共用): 如何正確設定 CORS?

2024/07/22

JAX-RS Jersey in Tomcat

在 java 開發 RESTful Web service 的方式,除了比較常見的 Spring MVC 以外,還有一個隸屬於 JSR-370 規格的 JAX-RS API,這份規格定義了在 java web container 裡面,應該提供什麼 API 介面輔助開發者開發 Representational State Transfer (REST) web service。Eclipse Jersey 則是實作了 JAX-RS API 的一組 library,以下記錄如何在 tomcat 10 裡面使用 Jersey

pom.xml

以下定義了 Maven POM xml,裡面主要引用了 jakarta.ws.rs-api JAX-RS API,以及三個 Jersey 實作的 libary,另外還用了 log4j2

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.maxkit</groupId>
    <artifactId>testweb</artifactId>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    <version>1.0</version>
    <name>testweb</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <!--junit5-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.10.1</version>
        </dependency>

        <!-- log4j2 + slf4j -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.22.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.22.1</version>
        </dependency>

        <!-- the REST API -->
        <dependency>
            <groupId>jakarta.ws.rs</groupId>
            <artifactId>jakarta.ws.rs-api</artifactId>
            <version>3.1.0</version>
        </dependency>
        <!-- These next two are needed because we are using Tomcat, which is not a full JEE server - it's just a servlet container. -->

        <!-- the Jersey implementation -->
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet</artifactId>
            <version>3.1.5</version>
        </dependency>

        <!-- also needed for dependency injection -->
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
            <version>3.1.5</version>
        </dependency>

        <!-- support for using Jackson (JSON) with Jersey -->
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-jackson</artifactId>
            <version>3.1.5</version>
        </dependency>

    </dependencies>
    <build>
        <finalName>testweb</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.12.1</version>
                    <configuration>
                        <source>${maven.compiler.source}</source>
                        <target>${maven.compiler.target}</target>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.3.1</version>
                    <configuration>
                        <encoding>${project.build.sourceEncoding}</encoding>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

application

有兩種方式定義 application,一種是在 web.xml,一種是直接寫在 Java code 裡面,以 annotation 定義

方法1 web.xml

以下用 org.glassfish.jersey.servlet.ServletContainer 定義了一個 JerseyRestServlet,並對應到 /rest/* 這個 url-pattern。

jersey.config.server.provider.packages 則是這個 application 會使用到的 web service 實作的 java classes 會放在哪一個 java package 裡面

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         id="testweb" version="3.0">
    <display-name>testweb</display-name>

    <servlet>
        <servlet-name>JerseyRestServlet</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.maxkit.testweb.jersey.rest</param-value>
        </init-param>
        <init-param>
            <param-name>jersey.config.server.provider.scanning.recursive</param-name>
            <param-value>true</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JerseyRestServlet</servlet-name>
        <url-pattern>/rest/*</url-pattern>
    </servlet-mapping>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Forbidden</web-resource-name>
            <url-pattern>/WEB-INF/*</url-pattern>
        </web-resource-collection>
        <auth-constraint />
    </security-constraint>
</web-app>

方法2 Annotation

透過 @ApplicationPath 這個 annotation,tomcat 在啟動時,會自動掃描,並定義一個新的 /rest2/* url-pattern 的 application。

這個 application 會使用到的 web service 實作的 java classes 會放在哪一個 java package 裡面,是用 packages() 定義

package com.maxkit.testweb.jersey;

import jakarta.ws.rs.ApplicationPath;
import org.glassfish.jersey.server.ResourceConfig;

@ApplicationPath("rest2")
public class Rest2Application extends ResourceConfig {

    public Rest2Application() {
        packages("com.maxkit.testweb.jersey.rest2");
    }
}

web service

JAX-RS 定義了一些 annotation,標註為 web service

  • @Path

    url method 的相對路徑

  • @GET,@PUT,@POST,@DELETE

    使用哪一個 http method

  • @Consumes

    可接受的 request 裡面資料的 MIME type

  • @Produces

    回傳的資料的 MIME type

  • @PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam

    參數的來源,@PathParam來自於URL的路徑,@QueryParam來自於URL的查詢參數,@HeaderParam來自於HTTP請求的頭信息,@CookieParam來自於HTTP請求的Cookie

@Path("/demo") 的部分,定義了對應的url,以 ping method 為例,要呼叫 ping,可使用這個 url: http://localhost:8080/testweb/rest/demo/ping

getNotification, postNotification 是測試要再回傳的資料中,以 JSON 格式回傳

login 裡面有用到 Cookie,因為 Jersey 的 Cookie 沒有 maxAge 的功能,這邊加上 maxAge,在 response 透過 Response.ResponseBuilder 設定 cookie

package com.maxkit.testweb.jersey.rest;

import com.maxkit.testweb.jersey.JerseyCookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.UUID;

//
// Pingable at:
// http://localhost:8080/testweb/rest/demo/ping
//
@Path("/demo")
public class DemoCommand {
    Logger log = LoggerFactory.getLogger(this.getClass().getName());

    @GET
    @Path("/ping")
    public Response ping() {
        log.debug("ping");
        return Response.ok().entity("Service online").build();
    }

    @GET
    @Path("/get/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getNotification(@PathParam("id") int id) {
        return Response.ok()
                .entity(new DemoPojo(id, "test message"))
                .build();
    }

    @POST
    @Path("/post")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response postNotification(DemoPojo pojo) {
        log.debug("demopojo={}", pojo);
        if( pojo.getTestdate() == null ) {
            pojo.setTestdate(new Date());
        }
        return Response.status(201).entity(pojo).build();
    }

    @POST
    @Path("/login")
    @Produces(MediaType.APPLICATION_JSON)
    public Response login(@Context HttpServletRequest req, @FormParam("userid") String userid, @FormParam("pd") String pwd) {
        JerseyCookie cookie = null;
        String cookiekey = UUID.randomUUID().toString();

        int validtime = 1 * 86400;
        cookie = new JerseyCookie("randomuuid", cookiekey, "/", null, 0, null, validtime, false, false);

        DemoPojo demo = new DemoPojo();
        demo.setId(12345);
        demo.setMessage("message");
        demo.setTestdate(new Date());
        Response.ResponseBuilder builder = Response.ok(demo);
        return builder.header("Set-Cookie", cookie).build();
    }
}

DemoPojo 是單純的一個 java data class

package com.maxkit.testweb.jersey.rest;

import java.util.Date;

public class DemoPojo {

    private int id;
    private String message;
    private Date testdate;

    public DemoPojo() {
    }

    public DemoPojo(int id, String message) {
        this.id = id;
        this.message = message;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Date getTestdate() {
        return testdate;
    }

    public void setTestdate(Date testdate) {
        this.testdate = testdate;
    }
}

JerseyCookie.java

package com.maxkit.testweb.jersey;

import org.glassfish.jersey.message.internal.HttpHeaderReader;
import org.glassfish.jersey.message.internal.StringBuilderUtils;

import jakarta.ws.rs.core.Cookie;
import jakarta.ws.rs.core.NewCookie;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
//import com.sun.jersey.core.impl.provider.header.WriterUtil;

public class JerseyCookie extends Cookie {
    public static final int DEFAULT_MAX_AGE = -1;

    private String comment = null;
    private int maxAge = DEFAULT_MAX_AGE;
    private boolean secure = false;
    private Date expires;
    private boolean httponly = false;

    public JerseyCookie(String name, String value) {
        super(name, value);
    }

    public JerseyCookie(String name, String value, String path, String domain, String comment, int maxAge,
            boolean secure) {
        super(name, value, path, domain);
        this.comment = comment;
        this.maxAge = maxAge;
        this.secure = secure;
    }

    public JerseyCookie(String name, String value, String path, String domain, int version, String comment, int maxAge,
            boolean secure) {
        super(name, value, path, domain, version);
        this.comment = comment;
        this.maxAge = maxAge;
        this.secure = secure;
    }

    public JerseyCookie(Cookie cookie) {
        super(cookie == null ? null : cookie.getName(), cookie == null ? null : cookie.getValue(),
                cookie == null ? null : cookie.getPath(), cookie == null ? null : cookie.getDomain(),
                cookie == null ? Cookie.DEFAULT_VERSION : cookie.getVersion());
    }

    public JerseyCookie(String name, String value, String path, String domain, int version, String comment, int maxAge,
            boolean secure, boolean httponly) {
        super(name, value, path, domain, version);
        this.comment = comment;
        this.maxAge = maxAge;
        this.secure = secure;
        if (maxAge > 0) {
            expires = new Date(new Date().getTime() + maxAge * 1000);
        }
        this.httponly = httponly;
    }

    public JerseyCookie(Cookie cookie, String comment, int maxAge, boolean secure, boolean httponly) {
        this(cookie);
        this.comment = comment;
        this.maxAge = maxAge;
        this.secure = secure;
        if (maxAge > 0) {
            expires = new Date(System.currentTimeMillis() + maxAge * 1000);
        }
        this.httponly = httponly;
    }

    public JerseyCookie(Cookie cookie, String comment, int maxAge, boolean secure) {
        this(cookie);
        this.comment = comment;
        this.maxAge = maxAge;
        this.secure = secure;
    }

    public static NewCookie valueOf(String value) throws IllegalArgumentException {
        if (value == null)
            throw new IllegalArgumentException("NewCookie is null");

        return HttpHeaderReader.readNewCookie(value);
        // return delegate.fromString(value);
    }

    public String getComment() {
        return comment;
    }

    public int getMaxAge() {
        return maxAge;
    }

    public boolean isSecure() {
        return secure;
    }

    public Cookie toCookie() {
        return new Cookie(this.getName(), this.getValue(), this.getPath(), this.getDomain(), this.getVersion());
    }

    @Override
    public String toString() {
        StringBuilder b = new StringBuilder();

        b.append(getName()).append('=');
        StringBuilderUtils.appendQuotedIfWhitespace(b, getValue());

        b.append(";").append("Version=").append(getVersion());

        if (getComment() != null) {
            b.append(";Comment=");
            StringBuilderUtils.appendQuotedIfWhitespace(b, getComment());
        }
        if (getDomain() != null) {
            b.append(";Domain=");
            StringBuilderUtils.appendQuotedIfWhitespace(b, getDomain());
        }
        SimpleDateFormat COOKIE_EXPIRES_HEADER_FORMAT = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z", Locale.US);
        COOKIE_EXPIRES_HEADER_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
        // String cookieExpire = "expires=" +
        // COOKIE_EXPIRES_HEADER_FORMAT.format(expires);
        if (getExpires() != null) {
            b.append(";Expires=");
            b.append(COOKIE_EXPIRES_HEADER_FORMAT.format(expires));
        }
        if (getPath() != null) {
            b.append(";Path=");
            StringBuilderUtils.appendQuotedIfWhitespace(b, getPath());
        }
        // if (getMaxAge() != -1) {
        // b.append(";Max-Age=");
        // b.append(getMaxAge());
        // }
        if (isSecure())
            b.append(";Secure");
        if (isHttponly())
            b.append(";HTTPOnly");
        return b.toString();
    }

    @Override
    public int hashCode() {
        int hash = super.hashCode();
        hash = 59 * hash + (this.comment != null ? this.comment.hashCode() : 0);
        hash = 59 * hash + this.maxAge;
        hash = 59 * hash + (this.secure ? 1 : 0);
        return hash;
    }


    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final JerseyCookie other = (JerseyCookie) obj;
        if (this.getName() != other.getName() && (this.getName() == null || !this.getName().equals(other.getName()))) {
            return false;
        }
        if (this.getValue() != other.getValue()
                && (this.getValue() == null || !this.getValue().equals(other.getValue()))) {
            return false;
        }
        if (this.getVersion() != other.getVersion()) {
            return false;
        }
        if (this.getPath() != other.getPath() && (this.getPath() == null || !this.getPath().equals(other.getPath()))) {
            return false;
        }
        if (this.getDomain() != other.getDomain()
                && (this.getDomain() == null || !this.getDomain().equals(other.getDomain()))) {
            return false;
        }
        if (this.getExpires() != other.getExpires()
                && (this.getExpires() == null || !this.getExpires().equals(other.getExpires()))) {
            return false;
        }
        if (this.comment != other.comment && (this.comment == null || !this.comment.equals(other.comment))) {
            return false;
        }
        if (this.maxAge != other.maxAge) {
            return false;
        }

        if (this.secure != other.secure) {
            return false;
        }
        if (this.httponly != other.httponly) {
            return false;
        }
        return true;
    }

    public Date getExpires() {
        return expires;
    }

    public boolean isHttponly() {
        return httponly;
    }

}

References

JAX-RS - 維基百科,自由的百科全書

Jakarta REST (JAX-RS) on Tomcat 10 - northCoder

Eclipse Jersey

2024/07/15

CBOR

RFC 8949 CBOR: Concise Binary Object Representation,這是一種輕量的二進位資料格式,可簡單理解為一種二進位的 JSON,設計目的是最小化處理程式碼,最小化訊息位元組大小。另外在 RFC 9052, 9053 CBOR Object Signing and Encryption (COSE) 制定了要如何對 CBOR 資料進行資料簽章與加密的方法。

online test

cbor.me

CBOR/web (plain)

可到這兩個網頁進行線上測試

測試時可以發現,不管是整數,字串,都有對應的編碼。甚至是 JSON 字串,也有對應的 Map (key-value pair) 的編碼方式

CBOR

header 有兩個資訊,前 3 bits 是 Major Type,後 5 個 bits 是 Additional Information

字節(byte)數 1 byte (CBOR Data Item Header) 動態長度 動態長度
結構 Major Type Additional Information Payload Length(可選) Data Payload(可選)
bit 3 Bits 5 Bits 動態長度 動態長度

CBOR 有八種 Major Type

首位元組的 lower 5 bits 在不同的主類型表示長度(除主類型 0 和主類型 1),如果長度指示不足,則依次使用後續位元組。

Major Type Name content
0 unsigned integer -
1 negative integer -
2 byte string N bytes
3 text string N bytes (UTF-8 text)
4 array of data items N data items
5 map of pairs of data items 2N data items (key/value pairs)
6 tag of number N 1 data item
7 simple/float -

比較特別的是 Major Type 6 裡面定義了 date time

0xC0 定義了 text-based date/time,0xC1 定義了 epoch-based date/time

還有 Major Type 7 裡面的

  • False 編碼後 0xF4
  • True 編碼後 0xF5
  • Null 編碼後 0b1110_0000 + 0b0001_01110 (22) = 0xF6

implementation

CBOR — Concise Binary Object Representation | Implementations

這邊列出了不同程式語言的 CBOR 實作 library

這邊用 js 測試

<html>
<script src="https://cdn.jsdelivr.net/npm/cbor-js@0.1.0/cbor.min.js"></script>
<script>

function hex(buffer) {
    var s = '', h = '0123456789ABCDEF';
    (new Uint8Array(buffer)).forEach((v) => { s += h[v >> 4] + h[v & 15]; });
    return s;
}

function test0() {
    console.log("");
    console.log("test0 unsigned integer");
    var initial = 1000;
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =" + initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =" + decoded);
}

function test1() {
    console.log("");
    console.log("test1 negative integer");
    var initial = -1000;
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =" + initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =" + decoded);
}

function test2() {
    console.log("");
    console.log("test2 byte string");
    var initial = new Uint8Array([0, 1, 2, 3]);
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test3() {
    console.log("");
    console.log("test3 text string");
    var initial = "text string";
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test4() {
    console.log("");
    console.log("test4 array");
    var initial = [1, "2"];
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test5() {
    console.log("");
    console.log("test5 map");
    var initial = { Hello: "World" };
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test6() {
    console.log("");
    console.log("test6 float");
    var initial = 3.1415;
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test7() {
    console.log("");
    console.log("test7 true");
    var initial = true;
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

function test8() {
    console.log("");
    console.log("test8 null");
    var initial = null;
    var encoded = CBOR.encode(initial);
    var decoded = CBOR.decode(encoded);
    console.log("initial =", initial);
    console.log("encoded hex=", hex(encoded));
    console.log("decoded =", decoded);
}

test0();
test1();
test2();
test3();
test4();
test5();
test6();
test7();
test8();

</script>
</html>

結果

test0 unsigned integer
initial =1000
encoded hex= 1903E8
decoded =1000

test1 negative integer
initial =-1000
encoded hex= 3903E7
decoded =-1000

test2 byte string
initial = Uint8Array(4) [0, 1, 2, 3, buffer: ArrayBuffer(4), byteLength: 4, byteOffset: 0, length: 4, Symbol(Symbol.toStringTag): 'Uint8Array']
encoded hex= 4400010203
decoded = Uint8Array(4) [0, 1, 2, 3, buffer: ArrayBuffer(5), byteLength: 4, byteOffset: 1, length: 4, Symbol(Symbol.toStringTag): 'Uint8Array']

test3 text string
initial = text string
encoded hex= 6B7465787420737472696E67
decoded = text string

test4 array
initial = (2) [1, '2']
encoded hex= 82016132
decoded = (2) [1, '2']

test5 map
initial = {Hello: 'World'}
encoded hex= A16548656C6C6F65576F726C64
decoded = {Hello: 'World'}

test6 float
initial = 3.1415
encoded hex= FB400921CAC083126F
decoded = 3.1415

test7 true
initial = true
encoded hex= F5
decoded = true

test8 null
initial = null
encoded hex= F6
decoded = null

references

# 物聯網專用資料交換格式 CBOR

cobr.io

CBOR_百度百科