(摘) ws4sqlite 远程sqlite数据库

声明:内容源自网络,版权归原作者所有。若有侵权请在网页聊天中联系我

ws4sqlite是通过http进行sqlite操作的工具. 类似于 PocketBase这种,将数据库与其它分开,可以实现于前端的分离.

但我依然不是太明白分享sqlite这种小型数据库的理由.似乎作者也在尝试支持其它数据库类型.

我能想到的好处就是便于稍大的团队分工: 将数据库的工作交给其它人来完成.或者在数据库没有搭建完善之前,“虚拟"返回数据.

象微服务这种开发理念,也越来越多团队在反省. 小团队不要想当然的微服务,用一些没有实际意义的概念.

ws4sqlite其实就是将数据服务放到服务器上,前端就可以通过认证和sql进行查询.它是通过REST来实现,就意味着javascript也可以直接调用数据库.

另外,它也实现了一个简单的静态http服务功能.

如果它能实现其它数据库的REST,或许意义更大一些.

github 一个go实现的客户端调用

运行: ws4sqlite –db mydatabase.db 提供一个对mydatabase.db的操作.此文件可以自动建立.

同时提供一个静态web服务: ws4sqlite –db mydatabase.db –serve-dir ..

同时提供多个数据库, 其中内存数据库mem1,且搜索配置文件meme1.yaml
ws4sqlite –db /file1.db –mem-db mem1:/mem1.yaml –mem-db mem2

这样就可以对三个数据库进行操作.当然也可以带多个–db

–db myFile.db:./myFileConfig.yaml 这为myFile.db指定了一个配置文件.
如果有与数据库名相同的yaml,它也会默认加载为配置文件.例如上面的 myFile.yaml

服务器建立后,就可以Post请求了.

地址: http://127.0.0.1:12321/mydatabase (这里以数据库名为ID,即mydatabase)
请求:

{
    "transaction": [
        {
            "statement": "CREATE TABLE TEST_TABLE (ID int primary key, VAL text)"
        }
    ]
}

多个请求:

{
    "transaction": [
        {
            "statement": "CREATE TABLE TEST_TABLE_2 (ID int primary key, VAL text)"
        },
        {
            "statement": "INSERT INTO TEST_TABLE_2 (ID, VAL) VALUES (1, 'hello')"
        }
    ]
}


{
    "transaction": [
        {
            "statement": "INSERT INTO TEST_TABLE (ID, VAL) VALUES (1, 'hello'), (2, 'world')"
        },
        {
            "query": "SELECT * FROM TEST_TABLE ORDER BY ID ASC"
        }
    ]
}

返回信息:

{
  "results": [
    {
      "success": true,
      "rowsUpdated": 2
    },
    {
      "success": true,
      "resultHeaders": [
        "ID",
        "VAL"
      ],
      "resultSet": [
        {
          "ID": 1,
          "VAL": "hello"
        },
        {
          "ID": 2,
          "VAL": "world"
        }
      ]
    }
  ]
}

查询:

{
    "transaction": [
        {
            "statement": "INSERT INTO TEST_TABLE (ID, VAL) VALUES (:id, :val)",
            "values": { "id": 101, "val": "A hundred and 1" }
        },
        {
            "query": "SELECT * FROM TEST_TABLE WHERE ID = :id",
            "values": { "id": 101 }
        }
    ]
}

通过配置文件,可以实现授权访问:

{
    "credentials": {                      // 仅当auth_mode == inline时才需要
        "user": "myUser1",
        "password": "myCoolPassword"
    },
    "transaction": [
        {
            "query": "SELECT * FROM TEMP"
        },
        {
            "query": "SELECT * FROM TEMP WHERE ID = :id",
            "values": { "id": 1 }
        },
        {
            "statement": "INSERT INTO TEMP (ID, VAL) VALUES (0, 'ZERO')"
        },
        {
            "noFail": true,
            "statement": "INSERT INTO TEMP (ID, VAL) VALUES (:id, :val)",
            "values": { "id": 1, "val": "a" }
        },
        {
            "statement": "#Q2",            // ‘#’ + 存储语句的 ID
            "valuesBatch": [
                { "id": 2, "val": "b" },
                { "id": 3, "val": "c" }
            ]
        }
    ]
}

默认情况下,它直接打开了WAL模式,以改善性能(多个读和写并发,减少IO,事务更快,崩溃恢复).

配置文件示例

auth:
  mode: HTTP   # 身份验证模式 HTTP:HTTP基本认证/INLINE:JSON请求中指定凭据  
  byCredentials:
    - user: myUser1
      password: myCoolPassword
    - user: myUser2
      hashedPassword: b133a0c0e9bee3be20163d2ad31d6248db292aa6dcb1ee087a2aa50e0fc75ae2
disableWALMode: true  # 禁止WAL模式
readOnly: false  # 是否为只读
corsOrigin: https://myownsite.com  # 可信访问,可以使用"*"
scheduledTasks:  # 计划任务
  - schedule: "0 0 * * *"
    doVacuum: true # 优化和清理内部结构
    doBackup: true  # 执行备份
    backupTemplate: ~/first_%s.db   # 备份文件的文件夹(带路径)。它必须包含一个 %s,该% s 将替换为 yyyyMMdd-HHmm 格式的备份分钟。
    numFiles: 3 # 只保存几个备份
    statements:  # 一组SQL,可以清理临时表等
      - DELETE FROM myTable WHERE tstamp < CURRENT_TIMESTAMP - 3600
      - ...
  - atStartup: true  # 启动时就执行任务
    doVacuum: true

useOnlyStoredStatements: true  # 指定一些语句存储在服务端,而无需客户端发送(这里的true将导致只能使用服务器保存的语句)
storedStatements:
  - id: Q1
    sql: SELECT * FROM TEMP 
  - id: Q2
    sql: INSERT INTO TEMP VALUES (:id, :val)
initStatements:    # 创建数据库时的初始化语句
  - CREATE TABLE AUTH (USER TEXT PRIMARY KEY, PASSWORD TEXT)
  - INSERT INTO AUTH VALUES ('myUser1', 'myCoolPassword')
  - CREATE TABLE TEMP (ID INT PRIMARY KEY, VAL TEXT)
  - INSERT INTO TEMP (ID, VAL) VALUES (1, 'ONE'), (4, 'FOUR')

将SQL语句存储到服务器有一些好处,比如较快的交互,安全性.
这样只需要客户端按id进行调用

{
    "transaction": [
        {
            "query": "#Q1"
        }
    ]
}

关于认证:

[...]
    auth:
      mode: HTTP
      customErrorCode: 499
      byCredentials:
        - user: myUser1
          password: myCoolPassword
        - user: myUser2
          hashedPassword: b133a0c0e9bee3be20163d2ad31d6248db292aa6dcb1ee087a2aa50e0fc75a[e2   # SHA-256 散列
 [...]
    auth:
      mode: INLINE
      byQuery: SELECT 1 FROM AUTH WHERE USER = :user AND PASSWORD = :password

在INLINE模式中, 查询 SQL 必须包含两个占位符 :user 和 :password,服务器将用客户端提供的用户名和密码替换它们。
如果查询至少返回一个结果,则凭据有效;如果返回0条记录,则访问将被拒绝。

请求格式如:

{
    "credentials": {
        "user": "myUser1",
        "password": "myCoolPassword"
    },
    [...]

如果令牌验证失败,将在1秒后返回响应,以防止暴力破解。

示例一个完整的请求:

{
    "resultFormat": "map",
    "credentials": {
        "user": "myUser1",
        "password": "myCoolPassword"
    },
    "transaction": [
        {
            "query": "SELECT * FROM TEMP"
        },
        {
            "query": "SELECT * FROM TEMP WHERE ID = ?",
            "values": [ 1 ] // Positional parameters
        },
        {
            "statement": "INSERT INTO TEMP (ID, VAL) VALUES (0, 'ZERO')"
        },
        {
            "noFail": true,  # 发生错误时不失败,继续执行并提交事务
            "statement": "INSERT INTO TEMP (ID, VAL) VALUES (:id, :val)",
            "values": { "id": 1, "val": "a" } // Named parameters
        },
        {
            "statement": "#Q2",
            "valuesBatch": [
                { "id": 2, "val": "b" },
                { "id": 3, "val": "c" }
            ]
        }
    ]
}

PS: 如何解决javascript保存了明文授权的问题? 思考: 不通过授权,通过服务器保存的sql进行敲门认证


一个小项目中使用中一下ws4sqlite.(个人还是很习惯于在每个项目中使用一些新技术)除了编辑配置文件yaml(主要是sql语句生成),基本就不需要后端了.余下的就是前端的html和javascript,确实减轻了一些工作量.
不过安全问题如何解决还没想到好的方法.

补充一下HTTP授权验证部份代码:

const username = prompt('请输入帐号:');
const password = prompt('请输入密码:');
            
// Base64编码凭证
const credentials = btoa(`${username}:${password}`);

const response = await fetch(WS4SQLITE_BASE_URL, {
      method: 'POST',
      headers: {
         'Content-Type': 'application/json',
         'Authorization': `Basic ${credentials}`
      }
});

相关文章