페이스트잇! - IPFS의 페이스트빈

23441 단어
IPFS은 InterPlanetary 파일 시스템을 나타냅니다. 급류의 아이디어와 비슷하지만 더 좋습니다. IPFS는 웹을 더 빠르고 안전하며 더 개방적으로 만들기 위해 설계된 P2P 하이퍼미디어 프로토콜입니다. 나는 IPFS에 대해 더 이상 괴짜하지 않을 것입니다. IPFS whitepaper을 읽으십시오.

나는 몇 년 전에 IPFS를 우연히 발견했고 그것이 흥미로웠다. 그 당시 IPFS에 액세스하는 유일한 방법은 자체 노드를 가동하는 것이었습니다(확실하지 않음, 연구 부족일 수 있음). 오늘날 우리는 여러 개의 무료 IPFS 엔드포인트를 보유하고 있습니다. 이러한 끝점을 사용하여 IPFS 네트워크와 상호 작용할 수 있습니다.

이 기사는 IPFS 네트워크에 텍스트 데이터를 저장하는 방법에 관한 것입니다. 이것은 무료로 데이터를 저장하기 위해 IPFS 네트워크를 사용하여 지난 며칠 동안 작업한 것입니다.

사용 도구


  • 빠른 API
  • 몽고디비
  • 날씬한
  • Infura IPFS 엔드포인트

  • 백엔드를 사용하는 이유는 무엇입니까?



    javascript의fetch API를 사용하여 IPFS 끝점에 대한 가져오기/게시 요청을 쉽게 만들 수 있습니다. 그러나 문제는 IPFS가 각 파일에 대한 해시를 생성한다는 것입니다. 이 해시는 파일 식별에 사용할 수 있습니다.

    QmR7GSQM93Cx5eAg6a6yRzNde1FQv7uL6X1o4k7zrJa3LX



    그러나 이러한 해시를 기억하는 것은 쉬운 일이 아니므로 데이터베이스를 사용하여 이러한 해시에 대한 별칭을 저장해야 합니다.

    FastAPI는 전체 프로그램 흐름을 규제합니다. 서비스 간 통신을 위한 API를 구축합니다.

    애플리케이션 구축



    설정 환경 변수




    # .env
    
    MONGO_CON_STRING=mongodb://localhost:27017/
    


    몽고디비 설정



    Docker를 사용하여 MongoDB를 가동해 봅시다. Docker는 로컬 설치 및 기타 기본 설정에 대한 오버헤드를 제거합니다.

    # pull MongoDB
    docker pull mongo
    
    # Start mongo container
    docker run -it -v mongodata:/data/db -p 27017:27017 --name ipfs-store -d mongo
    


    -v mongodata:/data/db


    -v는 볼륨을 지정하기 위한 것입니다. 컨테이너가 중지된 후에도 데이터를 유지하려면 MongoDB 저장소를 로컬 디렉터리에 매핑하는 것이 중요합니다. 컨테이너의 /data/db를 프로젝트 디렉토리의 mongodata에 매핑합니다. mongodata 폴더가 있는지 확인하십시오.

    # requirements.txt
    
    aiofiles==0.5.0
    fastapi==0.61.1
    ipfs-api==0.2.3
    pymongo==3.11.0
    sqlitedict==1.7.0
    uvicorn==0.12.2
    


    데이터베이스 코딩


    pymongo를 사용하여 데이터베이스와 통신합니다.

    # database/database.py
    
    class DataBase:
        def __init__(self) -> None:
            self.client = MongoClient(getenv("MONGO_CON_STRING"))
            self.db = self.client.pasteit
            self.col = self.db.links
    
        def set(self, short: str, hash: str) -> str:
            short_exists = self.col.find_one({"hash": hash})
            if short_exists is not None:
                return short_exists.get("short")
            data = {"short": short, "hash": hash}
            self.col.insert_one(data)
            return short
    
        def get(self, short: str) -> str:
            data = self.col.find_one({"short": short})
            if data is not None:
                return data.get("hash")
            return None
    
        def close(self) -> None:
            self.client.close()
    


    이와 같이 추상화를 생성하면 코드를 쉽게 읽을 수 있습니다. 작업을 완료하기 위해 일련의 pymongo 작업으로 setget 메서드를 정의했습니다.

    모든 데이터베이스 삽입은 이 형식입니다.

    {
        "short": "hash"
    }
    


    모든 삽입key: value을 기반으로 하고 있으므로 여기에서 Redis를 사용할 수도 있습니다. 이 응용 프로그램은 MongoDB atlas와 함께 vercel에 배포되기 때문에 MongoDB를 사용했습니다.

    위의 코드는 상당히 간단합니다. 제공된 short를 기반으로 해시를 가져오는 get 메서드를 만듭니다. set 쌍을 저장하기 위해 short: hash 메서드를 정의합니다. 그러나 먼저 해시가 이미 데이터베이스에 있지 않은지 확인합니다.

    IPFS 연결 만들기




    # ipfs/ipfs.py
    
    class IPFS:
        def __init__(self) -> None:
            self.ipfs = ipfsApi.Client("https://ipfs.infura.io", 5001)
    
        def add(self, text: str) -> str:
            filename = f"/tmp/{str(uuid4())}"
            with open(filename, "w") as f:
                f.write(text)
            res = self.ipfs.add(filename)
            remove(filename)
            print(res)
            return res[0].get("Hash")
    
        def cat(self, hash: str) -> str:
            data = self.ipfs.cat(hash)
            return data
    


    IPFS 끝점과의 통신은 페이로드가 있는 간단한 get/post 요청이지만 인코딩을 관리해야 합니다. 나는 우리를 위해 이미 기본적인 일을 한 라이브러리를 사용했습니다.

    입력 문자열을 파일에 쓴 다음 IPFS에 업로드하는 add 메서드를 정의합니다. cat 메서드는 해시를 사용하여 데이터를 읽습니다.

    서버 코딩



    서버에는 두 개의 엔드포인트가 있습니다. /api/v1는 업로드할 텍스트를 게시하고 /는 짧은 URL을 사용하여 데이터를 가져옵니다.

    # main.py
    
    async def connection() -> dict:
        return {"db": DataBase(), "ipfs": IPFS()}
    
    
    @app.post("/api/v1/")
    async def pasteit(data: Data, con: dict = Depends(connection)) -> dict:
        hash = con["ipfs"].add(data.text)
        short = str(uuid4())[:6]
        short = con["db"].set(short, hash)
        con["db"].close()
        return {"message": short}
    
    
    @app.get("/{short}")
    async def get_paste(short: str, con: dict = Depends(connection)) -> dict:
        hash = con["db"].get(short)
        if hash is not None:
            data = con["ipfs"].cat(hash)
            return {"message": data}
        con["db"].close()
        return {"message": "invalid short"}
    


    여기서는 모든 데이터가 성공적으로 업로드되었다고 가정합니다. 그런 다음 uuid.uuid4() 의 처음 6자를 사용하여 각 해시에 대한 사용자 정의 식별자를 생성합니다. 이 짧은 생성 방법에 대해 충돌 테스트를 수행해야 합니다.

    # collision_test.py
    
    from uuid import uuid4
    
    
    def get_id() -> str:
        return str(uuid4())[:6]
    
    
    def test_n(n: int) -> None:
        outputs = [get_id() for _ in range(n)]
        unique_outputs = set(outputs)
        fraction = 1 - (len(unique_outputs) / len(outputs))
        print(f"Test for {n} shorts, collision: {fraction*100:.2f}")
    
    
    if __name__ == "__main__":
        test_n(100)
        test_n(1000)
        test_n(10000)
        test_n(100000)
        test_n(1000000)
    



    -> python collision_test.py
    Test for 100 shorts, collision: 0.00
    Test for 1000 shorts, collision: 0.00
    Test for 10000 shorts, collision: 0.05
    Test for 100000 shorts, collision: 0.26
    Test for 1000000 shorts, collision: 2.93
    
    -> python collision_test.py
    Test for 100 shorts, collision: 0.00
    Test for 1000 shorts, collision: 0.00
    Test for 10000 shorts, collision: 0.01
    Test for 100000 shorts, collision: 0.27
    Test for 1000000 shorts, collision: 2.92
    

    n=1,000,000 를 제외하고는 테스트가 통과된 것 같습니다. ~30,000번의 충돌이 발생했습니다. 그러나 단기간에 그렇게 많은 요청을 받지는 않을 것이라고 가정하는 것이 안전합니다.

    프론트엔드




    # src/App.svelte
    
    <script>
        let data = "";
        let hash = "";
        const upload = () => {
            fetch("http://localhost:8000/api/v1", {
                method: "POST",
                body: JSON.stringify({ text: data }),
            })
                .then((res) => res.json)
                .then((data) => (hash = data.message));
        };
    </script>
    
    <textarea id="data" bind:value={data} />
    <button id="upload" on:click={upload}>Upload</button>
    <p>{hash}</p>
    


    이 코드는 프런트엔드 빌드에 대한 공정한 아이디어를 제공합니다. 현재 텍스트 제한은 200자로 설정되어 있습니다.

    Pastit의 다음 단계는 무엇입니까! ?



    이것을 IPFS에서 파일 공유 서비스로 전환할 계획입니다. 사람들의 관심을 끌기 위해 약간의 암호화를 추가할 수도 있습니다!!

    데모



    https://pasteit.vercel.app/에서 최종 지원서를 확인하세요.



    GitHub : https://github.com/amalshaji/pasteit

    좋은 웹페이지 즐겨찾기