├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── architecture.PNG ├── architecture.xml ├── build.sh ├── cmd ├── cert.pem ├── config.json └── key.pem ├── db ├── initFromJsons.go └── replayDB.go ├── getversion.sh ├── main.go ├── middleware ├── middlehandler.go ├── middleware.go ├── pactgenerate.go └── test.json ├── offline ├── logger.go ├── modals.go ├── repo.go ├── repo_test.go ├── sample_handler.go └── sample_routes_define.go ├── online └── proxyHandler.go ├── utils ├── helper.go ├── helper_test.go └── jsonrelated.go ├── version.go └── workflow.PNG /.gitignore: -------------------------------------------------------------------------------- 1 | MockXServer/cmd* 2 | MockXServer/pacts/ 3 | MockXServer/*DB 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | script: sh ./build.sh 3 | notifications: 4 | email: false 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | default:build 2 | build: 3 | @bash getversion.sh 4 | @bash build.sh 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MockXServer 2 | [![Build Status](https://travis-ci.org/compasses/MockXServer.svg?branch=master)](https://travis-ci.org/compasses/MockXServer) 3 | ## Release 4 | 5 | ## 说明 6 | Auto-record/ Auto-replay API请求;支持oneline 和 offline 两种工作模式,支持http和https方式。 7 | RunMode为online时,作用相当于一个API Proxy,并自动记录请求/响应记录到一个文件数据库:ReplayDB; 8 | RunMode为offline时,作用类似于MockServer,默认从ReplayDB中请求响应,如果找不到对应的响应,然后会根据OfflineHandler配置项是否从offline handler中请求对应的响应; 9 | 使用场景常在开发、测试中使用,因能够自动Record和Replay会节省很多准备真实环境的时间。 10 | 特别在两个团队分前后台开发的时候,把后台服务直接replay或通过offline handler simulate出来,两个团队之间只进行API接口编程,有助于提升开发效率。 11 | 12 | ## PACT record & Json 文件生成 13 | 1. 通过请求http(s)://MockXServer_IP:Port/pact生成pact文件,目前只支持1.1 的版本 14 | 2. 通过请求http(s)://MockXServer_IP:Port/json生成json文件,里面记录了当前record的所有请求/响应记录。 15 | 16 | ## offline 17 | 1. RESTFul资源服务器,作为离线使用需要完成正常的所有功能。 18 | 2. 自带存储,需要存储可能重复使用的信息。保证功能的完备性 19 | 20 | ## online 21 | 1. 类似API的proxy,直接将请求路由给目的服务器。 22 | 2. 目前是主要记录请求和响应信息,http相关的能够完整的记录下来。 23 | 3. 支持https,所以这个proxy可以将请求做二次处理,并能记录详细信息。 24 | 25 | ## config.json说明 26 | 1. RunMode, 表示offline和online; 27 | 2. TLS,是否起用https模式; 28 | 3. RemoteServer,online时,请求的远程服务器; 29 | 4. ListenOn, APIService的监听地址; 30 | 5. OfflineHandler, 当runMode为offline时,是否启用offline handler响应请求; 31 | 5. LogFile, 日志文件名字。留空的话直接打印到命令行窗口。 32 | 33 | ## 运行 34 | 在 Mac或Linux 上执行make 即可,在windows上执行 *sh buil.sh*。可执行文件位于cmd目录下。 35 | 36 | ## 简单使用说明 37 | 1. 打开online模式,运行mock server,会将请求、响应消息信息保存到ReplayDB文件中; 38 | 2. 分享给其他开发者ReplayDB,只需配置为offline模式,所有online模式中记录的API都能正常的响应。 39 | 40 | ### example 41 | 一个Get请求的记录: 42 | ``` 43 | "/api/CreditCards/v1/getCardsByCustomerId/120016694099968": { 44 | "GET": [ 45 | { 46 | "request": null, 47 | "response": { 48 | "200": "[{\"id\":120041944596480,\"last4Digits\":\"0005\",\"cardType\":\"AMERICANEXPRESS\",\"nameOnCard\":\"Jet 1\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120043117346816,\"last4Digits\":\"5904\",\"cardType\":\"DINERSCLUB\",\"nameOnCard\":\"Jet 2\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120045496467456,\"last4Digits\":\"1117\",\"cardType\":\"DISCOVER\",\"nameOnCard\":\"Jet 3\",\"expiryYear\":\"2222\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120048522469376,\"last4Digits\":\"4444\",\"cardType\":\"MASTERCARD\",\"nameOnCard\":\"Jet 4\",\"expiryYear\":\"2322\",\"expiryMonth\":\"12\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null},{\"id\":120049814609920,\"last4Digits\":\"5100\",\"cardType\":\"MASTERCARD\",\"nameOnCard\":\"Jet 5\",\"expiryYear\":\"2323\",\"expiryMonth\":\"11\",\"customerId\":120016694099968,\"creationTime\":null,\"updateTime\":null}]" 49 | } 50 | } 51 | ] 52 | } 53 | ``` 54 | 55 | 两次Post请求的记录: 56 | ``` 57 | "/api/CreditCardCheckout/v1/checkout": { 58 | "POST": [ 59 | { 60 | "request": { 61 | "amount": 100, 62 | "currencyCode": "USD", 63 | "id": 118650906992640, 64 | "paymentAccountId": 118628324237312 65 | }, 66 | "response": { 67 | "200": "{\"pnref\":\"A10AA261D8C8\",\"paymentResponse\":{\"requestId\":\"FE60350123E15D3A8DF6D44BE67110CF\",\"result\":0,\"respMsg\":\"Approved\",\"status\":true,\"authCode\":\"040PNI\",\"avsAddr\":null,\"avsZip\":null,\"preFpsMsg\":null,\"postFpsMsg\":null,\"transError\":null,\"pnref\":\"A10AA261D8C8\"}}" 68 | } 69 | }, 70 | { 71 | "request": { 72 | "amount": 222, 73 | "currencyCode": "USD", 74 | "id": 118650906992640, 75 | "paymentAccountId": 118628324237312 76 | }, 77 | "response": { 78 | "200": "{\"pnref\":\"A70AA0C6F56C\",\"paymentResponse\":{\"requestId\":\"62B4918913F4A4FE7BB826888BC7B29A\",\"result\":0,\"respMsg\":\"Approved\",\"status\":true,\"authCode\":\"537PNI\",\"avsAddr\":null,\"avsZip\":null,\"preFpsMsg\":null,\"postFpsMsg\":null,\"transError\":null,\"pnref\":\"A70AA0C6F56C\"}}" 79 | } 80 | } 81 | ] 82 | } 83 | ``` 84 | 上面的结果是通过访问**http(s)://MockXServer_IP:Port/json**获得的json记录。 85 | 86 | 87 | ## 结构图: 88 | ![architecture](./architecture.PNG) 89 | 90 | 整体结构较为简单,MiddleWare层封装了offline和online。offline和online会各自访问DB进行读写。 91 | 92 | ### Third party lib 93 | 1. [httprouter](http://godoc.org/github.com/julienschmidt/httprouter) 94 | 2. [boltDB](http://godoc.org/github.com/boltdb/bolt) 95 | 3. [PACT-go](https://github.com/SEEK-Jobs/pact-go) 96 | -------------------------------------------------------------------------------- /architecture.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compasses/MockXServer/1169543ae7d372309fc9dbbdf9aaba32a7b95c91/architecture.PNG -------------------------------------------------------------------------------- /architecture.xml: -------------------------------------------------------------------------------- 1 | 7Vxdc6LMEv41qTrvRSwQMOYyms3uqbf2nFSSevecSyKjUouMC5iP/fXbDd04w2CiQqIxeqEywOD00/30x8x44gxnT18Tfz79LgMRnXSt4OnEuTzpdm3XceADW56LlvO+XTRMkjAomqxlw234W9Cd3LoIA5FSW9GUSRll4VxvHMk4FqNMa/OTRD7ql41lRE+lhrk/4ScuG25HfmS2/giDbFq09ru9Zfs3EU6m/GS7d16cufdHPyeJXMT0vJOuM85fxemZz33RQNOpH8hHpcn5AnJNpISe8dvsaSgilK0utqsVZ8vfnYiYftvLN8ApvOHBjxY09P+Ox1EYC/p52TOL5HEaZuJ27o/w+BFgP3EG02wWwZENX9MskT/FUEYyya93rq4ceMGZcRhF3B5L6NkZTBI/COEXVprHMs5IF+weHWs9WvCCdj8KJzG0jaALAScH5qhJEA8iyQRrZSleUFshZyJLnuESPntOt5DGdnskmscl/rZNKE8V7HuEpE8qNym7XoodvpDk61Ega1FRiD8jCJ5FBrYLEDwDBEP8YK1z/JqG8SQSF0g0MPAgTICDQonSSOUCf+hgU6BQqMPhxwHKJYETUHafgFOAcugSFaczEnITnOhJCk43Yh75z5c0sBq8Rs9gTUE++AMHxrWJSwgYzyFrUYBhptOA6TcH5myVLzkik4c2Ord5LsnrFWSYApsgQ+BqJuPjBRfX/4b3W5HAOFZjtLhH4R06pXk6pdVZThkvqQD1W/A99CgFoAKZoY+X9SJ4FLiZB/g6wa/cdA+g6S3wIO26CqQgC2xXYEtEGv727/MLULRzGcZZPhJvcOJdorAXmYRrclAU2UdijF2hgEMInC+oOZOoFymoCfjIOzy4PHU3gLYNJHuVKMIi/VeQ7FGTCqTTAgdyAnOMItYAqusQBZZRBIUH7xBF2PxrjybXBpJsT7swOTOHNUAQcUB2djmK/DQNR2snTi8LsbQ48RRm/0NAO6CdxeH/qfOV8gUzT3LTXibimZ9MBHv+okkEWq3CxECRMfswVcbclojIz8IHvcJRJ3h6wjWqpRq/aAhXM65iLHSPWnCodMP3sclXuinGb3ST60A55PXUwkyq7Q4c3+Uy+wpC7lo3Ip3LOIU8u87e/RnaVXyf4sc4kbP8Dk419NNvSQZV5q3a/ywMArx/EPn3IhqUFShFTakGtb46JzIDZcmdkYf6XKPDbHZVkiircjQqrbJVRx6nVsf27AqBNNNZ1i79Bjkep4h7M71yDb36W8C9F9B2jeW9LnRhfRfZVAbF9xvxayHSbCChGmoq2j8QiOc3szKq122sTjqRsY83o4FXXEqpUnURiISrx1FOplO4TsAN7+lrnAoT2ZaZSNU6G7eNsGGNKtGbOZtSiiC85Bm9DXaXH6CvUVxP4Yl6FoYB7IqsjgUupTi+FkkIY0c6yVlnLRdF/kh1UTT4PXFRbk+PJz2OL/fXSZnFrC46qRuRLZJ4Mw8VwhwDzHTg1MPnc0Zslo2dkdVxbU7HCf5TiiS21c523Y9ZZdslAZUHBcNgHyX/aNxzDj+8AffUhMckiGN4vJ0emTVBB5nnPzDz2LWuChqxiqi3PtRVqEWJig+VeGyAor5IU6O/bKNthMae1de92mnD2Ji7rhRfeAanXbYyC5u7yM1LturALJ9CWHn4tEzcofyuUpaTO5utKYvEu7/h0kejLF4zoSiTqwVLaTibg1Ztk9eDthcT7tYMFrMcMo+tSsPYUluIoOxyxokpi3LmvYigyoxzXzjp3IEBq1GUVZJUlXiYrHSiytO+tTiJIlmVkwipIydtp0xm8bmFKmNSzM6mNC/7aUqNANvaMdYeVx9ZCfvUD/Xq8gqAdunMrHPvls688zzTW9LZeT9PC1/is8qsydoxFsGm8hmJ48hn22mTWd/MC1Jf87Qwm2KEBDzSkM8Omr9MxS1NtIXYyjnrUjClz3E01VO9U6dSKm+Hqcxi5y6ZqlI710pXna3mcGuiq+McbiONMYudeZHqNpMJMtHGfJTJ/HqsVgX3B53l1acEpQm2wEQW2t0epXVmQbPx8vn211OVPVYmYNtYYOrqy95sp28uwvHOapaY2h4B2WRmtGYNo2aQCg69Xwvc3zJAyzolQeAMOFlLeZ5N8ntuLD9AOGua7B7i3OpS4q2R5m1YjTarvN2eIZSajVIrzvDWK7z2gyLj8sYwdXECrxjW1jS2sMyb+bj9fUQfHhiXPd+LwNTujGgDGHPN0FDG43ByBIZtoaQyh7pQV44SdiosRtS5FS5mtluzV+XTY+K5NZjw9GHreyHXyBJV6fMG2rx06KdTgf2oq6Zfj8F5Qf7sCcCYTzvFDuJu8YndoiBgORV+RRHB5rf6MmUssxHKB8/XgL8mpJCdhPFpggOpU5iVEbyB+eoldK6+S8x2PRNhXlOjAdxG/LDGCpYjwM0A9ngJwhJgMs33ANjMxB4ToNAdFHtqqy+bCFLfFO7CigAjzn7D9YxQONeJuNrHuhUbsyfODl4p2pjLRrp9vVTosvm2ms3z5rtXdejDVGc2LLzoSlqY1AvFmMqEe8P5dsZaR/q0GnK1gjTvJD88tuhx2P8+bOFY+rLV7dnC7GlrtrCZMVkxecVzuzpkLumAKSdYwPgpyaKwqMMkC7P29O3u7vpf6V+rt7Fvmr7ZQ+vCQuR3mL41Cv7M8mBNTl1bHvQq+yXWiP7gcPlvRQWiy7+Ecr78AQ== -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set - ex 4 | 5 | # Create go binary and package verifier + mock service into distribution 6 | VERSION=$(go version) 7 | echo "==> Go version ${VERSION}" 8 | 9 | echo "==> Getting dependencies..." 10 | export GO15VENDOREXPERIMENT=1 11 | 12 | go get github.com/mitchellh/gox 13 | go get github.com/inconshreveable/mousetrap # windows dep 14 | go get -d ./... 15 | gox -os="darwin" -arch="amd64" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}" 16 | gox -os="windows" -arch="386" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}" 17 | gox -os="linux" -arch="386" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}" 18 | gox -os="linux" -arch="amd64" -output="cmd/MockXServer_{{.OS}}_{{.Arch}}" 19 | 20 | echo 21 | echo "==> Results:" 22 | ls -hl cmd/ 23 | #echo "packaging ..." 24 | #tar -czf MockXServer.tar.gz MockXServer/ 25 | -------------------------------------------------------------------------------- /cmd/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC9zCCAd+gAwIBAgIQIiLjW/T/z/C82zPm6RDjlzANBgkqhkiG9w0BAQsFADAS 3 | MRAwDgYDVQQKEwdBY21lIENvMB4XDTE1MDkyOTExMTU1N1oXDTE2MDkyODExMTU1 4 | N1owEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBALEvsJ3DrrJrZmcSwrTD7nxvnbQRnikfENmt0t9Y3BrSJ/BGog62m1zc 6 | 5ZneJOv1C314D/H0wqdcpPRUenAkPUTa6Yj6RuUFIS7i8LgjsQ/ZR6HDSIDFd9ax 7 | AnZKWDIzBrcSadZvLoMq4epTjCFRXnQ+S2tqA7yqcrzIfyF3rSQPaU27NNZMwfRR 8 | Vgn1vHHHadf67KYqaTyQaZwhTI4cggHVYSEuTBejAUBabgFfHpG/G0mJ9e7+DOD0 9 | Au6hxXCh7SVoo3PKLQ4OHKdNhXITxm9u24tOszVf2Y/WaOsv1ZkJ9esaAeQ4Qmts 10 | zEIb+cJdP1U1x/ePE9q6idZZXwQShKMCAwEAAaNJMEcwDgYDVR0PAQH/BAQDAgWg 11 | MBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwEgYDVR0RBAswCYIH 12 | amV0aG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAk0QwFO971DgGA9XSZlkW5lxqDVHZ 13 | C8R7RUotlamAVFW/Mx+kht/4ifaON3AvXTuizyR2rgMCvC8sT/2Z/zHWh+UYwrhf 14 | lPak9YK1UXxOI0XKZxIu82MX59vB9dvaRfPaSyaeEIOs+FtuQ0NRiUyXIrEY916X 15 | kMdhoLWtlwFsChzgSWYzWxzpZotdaSlFncu/wosX1F4jy/Y0n7Fc2zzMWatCS3cP 16 | DrHnNmWtCvBWPZ+kvuui/pWa6r0khPTK4NdbOuFMIvqq3cgNipH4yk7PAEZGVnwh 17 | 4HIHba/0Jj8mQJOn9blGTkfTwjRir/mcVKAorSj/yMsVxIQUVr5x1W1WrA== 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /cmd/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "RunMode":"online", 3 | "TLS":"off", 4 | "RemoteServer":"http://localhost:30008", 5 | "ListenOn":"0.0.0.0:9595", 6 | "OfflineHandler":"off", 7 | "LogFile":"" 8 | } 9 | -------------------------------------------------------------------------------- /cmd/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAsS+wncOusmtmZxLCtMPufG+dtBGeKR8Q2a3S31jcGtIn8Eai 3 | DrabXNzlmd4k6/ULfXgP8fTCp1yk9FR6cCQ9RNrpiPpG5QUhLuLwuCOxD9lHocNI 4 | gMV31rECdkpYMjMGtxJp1m8ugyrh6lOMIVFedD5La2oDvKpyvMh/IXetJA9pTbs0 5 | 1kzB9FFWCfW8ccdp1/rspippPJBpnCFMjhyCAdVhIS5MF6MBQFpuAV8ekb8bSYn1 6 | 7v4M4PQC7qHFcKHtJWijc8otDg4cp02FchPGb27bi06zNV/Zj9Zo6y/VmQn16xoB 7 | 5DhCa2zMQhv5wl0/VTXH948T2rqJ1llfBBKEowIDAQABAoIBADe1M8VWac2k4MxV 8 | ZKwq0geDnESqKVzqbITUFvGr4X/5RR9RRdB0b0JyHSoUYu1g3Nz066Z5+t1dzmsX 9 | SGuLqUvFvBkZ/0IwK6+vIFn0ts2x1cixOlFqRHRWsNx6IWKfEmRqwKXvzOLmj6L5 10 | 45vyFk1b7KB383bU7EAYlYfzNZeZTuOW6Ool2MkglQEtk3kJXt946/1k12lliErS 11 | WLam7iAWT/aLNyQrXrycRZvcBSOIRj/+C/wgSMm3WgYOuikXOJAsGZHMLpKy4LYz 12 | p9C+CaVAAWSq0AVQD3Ph/uyVpzkJkh6jHVMxyTGk3ZXBccXNviXZ+GiBHgfCivaQ 13 | RvhoZ8kCgYEA2MOM+6hydiJLaIx4zm51LJM+hQMDmN7CsTrahzWviv16eDzglTLV 14 | SNsnssU9+9VaMGd3ef+uC8C4ZTWqTe+J3Q39pmsPIrl5O5EIrsR/DJp8N0gE0sf7 15 | 7/wZau1xWutgkabzteN9l9d+WJ1Aj0MkNZXNlsF/JA/1q5j8Esqg2Y8CgYEA0UIv 16 | XDCQbEKea5k2J1Fr6KZBfJ4owSgB9I7qN3LxeLAWxmRFMgMy/akItcqdSlmHq62T 17 | jyDFEORldsSGNJPZiUv58bEuO45ynucHE+ycT+/jQ9ScjQwSHVY+1uwhwC7Zy1Wj 18 | 0ZiZz96v2x3+DEs4pB9+elvZnJBn07PjVeHDEa0CgYB7iriHlfHmspUX+TfBL+NI 19 | jINNe/JI5m1AZbK27TonlWB/tvKpFlfJNl7h3Nj+9xJ4FABcYATqXeS6imOOF8AT 20 | ZS3z84Ck4eDeukKgmQ1wOXwiZcGaALDujlhOQRYiI5TebrDoMcgbCo7QIo/xFutE 21 | PyT1j0LPYuk56+9/Jk2WsQKBgAv+KxCxh9Vsi//GrB/cvr56qM4nklZfoJ0Pg8Th 22 | xY0RKnz8SirvnACFq4YfspwMPYZNUGOnkSPCsz33TWm9/32q6Qw9B1VKeK86tmEh 23 | 8mNycCf3C3HZ+z/LLZbLubN1++13XwgoIsiTDqkUMvcpaHmfhHQmx3X9oLW1XZYS 24 | dkeRAoGBAKjMIbKOOkTUSBLDhGMvVCfm0nnfPYVigZuJyf265nCbPWW9QBOPXieK 25 | 2+pbleuOvi8QipOVd/b09Qwa05dYS/LJY87c/nwD1ijqa63sWJ/KmOQxQ/edVqRW 26 | wKVouqMTmhpWuf8V+Lo78UbTkKFHystFnmOxmbcdPqnZZTJoMc3F 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /db/initFromJsons.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "time" 9 | 10 | "github.com/compasses/MockXServer/utils" 11 | ) 12 | 13 | func (replay *ReplayDB) ReadJsonFiles(filePath string) { 14 | now := time.Now() 15 | log.Println("going to read file", filePath) 16 | stream, err := ioutil.ReadFile(filePath) 17 | if err != nil { 18 | log.Println("going to read file error happened ", err, stream) 19 | return 20 | } 21 | 22 | res, err := utils.TOJsonInterface(stream) 23 | paths := res.(map[string]interface{})["paths"] 24 | pathsMap := paths.(map[string]interface{}) 25 | for path, val := range pathsMap { 26 | valMap := val.(map[string]interface{}) 27 | for method, detail := range valMap { 28 | detailMap := detail.([]interface{}) 29 | for _, detailMapel := range detailMap { 30 | detailMapItem := detailMapel.(map[string]interface{}) 31 | request, ok := detailMapItem["request"] 32 | if !ok { 33 | log.Println("missing request, continue ", detailMapItem) 34 | continue 35 | } 36 | respose, ok := detailMapItem["response"] 37 | if !ok { 38 | log.Println("missing response, continue ", detailMapItem) 39 | continue 40 | } 41 | responseMap := respose.(map[string]interface{}) 42 | for k, v := range responseMap { 43 | status, _ := strconv.Atoi(k) 44 | //fmt.Println("\r\nstore:", request, "response", v) 45 | replay.StoreRequestFromJson(path, method, request, v, status) 46 | break 47 | } 48 | } 49 | } 50 | } 51 | log.Println("Time Used:", time.Since(now)) 52 | } 53 | 54 | func (replay *ReplayDB) ReadDir(dir string) { 55 | files, err := ioutil.ReadDir(dir) 56 | if err != nil { 57 | log.Println(err) 58 | return 59 | } 60 | 61 | for _, file := range files { 62 | if strings.Contains(file.Name(), "Incomplete") { 63 | continue 64 | } 65 | replay.ReadJsonFiles(dir + "/" + file.Name()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /db/replayDB.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | 9 | "github.com/boltdb/bolt" 10 | "github.com/compasses/MockXServer/utils" 11 | ) 12 | 13 | type ReplayDB struct { 14 | db *bolt.DB 15 | reqRspKey string 16 | } 17 | 18 | func NewReplayDB(path string) (*ReplayDB, error) { 19 | dbopen, err := bolt.Open(path, 0600, nil) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | return &ReplayDB{db: dbopen, reqRspKey: "|"}, nil 25 | } 26 | 27 | func (replay *ReplayDB) GetDBFilePath() string { 28 | return replay.db.Path() 29 | } 30 | 31 | func (replay *ReplayDB) StoreRequestFromJson(path, method string, reqBody, respBody interface{}, statusCode int) { 32 | req := utils.JsonInterfaceToByte(reqBody) 33 | rsp := utils.JsonInterfaceToByte(respBody) 34 | 35 | err := replay.StoreRequest(path, method, string(req), string(rsp), statusCode) 36 | if err != nil { 37 | log.Println("Store for json failed ", err) 38 | } 39 | } 40 | 41 | func (replay *ReplayDB) StoreRequest(path, method, reqBody, respBody string, statusCode int) (err error) { 42 | if len(reqBody) == 0 { 43 | reqBody = replay.reqRspKey 44 | } 45 | 46 | finalReq, finalResp, err := utils.JsonNormalize(reqBody, respBody, statusCode) 47 | if err != nil { 48 | log.Println("JSON Normalize error ", err) 49 | } 50 | log.Println("going save req: ", path, " rqbody ", reqBody) 51 | 52 | replay.db.Update(func(tx *bolt.Tx) error { 53 | pathBucket, err := tx.CreateBucketIfNotExists([]byte(path)) 54 | if err != nil { 55 | err = fmt.Errorf("create bucket: %s", err) 56 | return err 57 | } 58 | 59 | err = pathBucket.Put([]byte(method+replay.reqRspKey), finalResp) 60 | if err != nil { 61 | err = fmt.Errorf("store bucket error: %s", err) 62 | return err 63 | } 64 | 65 | methodBucket, err := pathBucket.CreateBucketIfNotExists([]byte(method)) 66 | err = methodBucket.Put(finalReq, finalResp) 67 | if err != nil { 68 | err = fmt.Errorf("store bucket error: %s", err) 69 | return err 70 | } 71 | return nil 72 | }) 73 | return 74 | } 75 | 76 | func (replay *ReplayDB) GetResponse(path, method, reqBody string) (resp []byte, err error) { 77 | if len(reqBody) == 0 { 78 | reqBody = replay.reqRspKey 79 | } 80 | 81 | replay.db.View(func(tx *bolt.Tx) error { 82 | 83 | pathBucket := tx.Bucket([]byte(path)) 84 | if pathBucket == nil { 85 | err = fmt.Errorf("No response found for path:%s", path) 86 | return err 87 | } 88 | methodBucket := pathBucket.Bucket([]byte(method)) 89 | if methodBucket == nil { 90 | err = fmt.Errorf("No response for path and method:%s", path+method) 91 | return err 92 | } 93 | 94 | req := []byte(reqBody) 95 | // req, err = utils.JsonNormalizeSingle(reqBody) 96 | // if err != nil { 97 | // return err 98 | // } 99 | resp = methodBucket.Get(req) 100 | if resp == nil { 101 | //use fuzzy match to get result 102 | resp = pathBucket.Get([]byte(method + replay.reqRspKey)) 103 | if resp == nil { 104 | err = fmt.Errorf("No response for path and method:%s", path+method) 105 | } 106 | } 107 | return nil 108 | }) 109 | return 110 | } 111 | 112 | func (replay *ReplayDB) Close() { 113 | err := replay.db.Close() 114 | if err != nil { 115 | fmt.Println("Close error:", err) 116 | } 117 | } 118 | 119 | func (replay *ReplayDB) Open(path string) { 120 | db, err := bolt.Open(path, 0600, nil) 121 | replay.db = db 122 | if err != nil { 123 | log.Println("Open DB error:", err) 124 | } 125 | } 126 | 127 | func (replay *ReplayDB) GetDB() *bolt.DB { 128 | return replay.db 129 | } 130 | 131 | func (replay *ReplayDB) GetJSONMap() (outmap map[string]map[string][]interface{}) { 132 | outmap = make(map[string]map[string][]interface{}) 133 | 134 | replay.db.View(func(tx *bolt.Tx) error { 135 | cur := tx.Cursor() 136 | buc := cur.Bucket() 137 | c := buc.Cursor() 138 | 139 | for k, v := c.First(); k != nil; k, v = c.Next() { 140 | //fmt.Printf("Out side key=%s, value=%s\n", k, v) 141 | if v == nil { 142 | bb := tx.Bucket(k) 143 | if bb == nil { 144 | continue 145 | } 146 | cc := bb.Cursor() 147 | for kk, vv := cc.First(); kk != nil; kk, vv = cc.Next() { 148 | //fmt.Printf("middel layer key=%s, value=%s\n", kk, vv) 149 | if vv == nil { 150 | bbb := bb.Bucket(kk) 151 | if bbb == nil { 152 | continue 153 | } 154 | ccc := bbb.Cursor() 155 | for kkk, vvv := ccc.First(); kkk != nil; kkk, vvv = ccc.Next() { 156 | //fmt.Printf("last layer key=%s, value=%s\n", kkk, vvv) 157 | if outmap[string(k)] == nil { 158 | outmap[string(k)] = make(map[string][]interface{}) 159 | } 160 | var pamv interface{} 161 | var resp interface{} 162 | 163 | err := json.Unmarshal(kkk, &pamv) //string(kkk) 164 | if err != nil { 165 | log.Println("Unmarshal failed ", err) 166 | } 167 | err = json.Unmarshal(vvv, &resp) 168 | if err != nil { 169 | log.Println("Unmarshal failed ", err) 170 | } 171 | outmap[string(k)][string(kk)] = append(outmap[string(k)][string(kk)], map[string]interface{}{ 172 | "request": pamv, 173 | "response": resp, 174 | }) 175 | } 176 | } 177 | } 178 | } 179 | } 180 | return nil 181 | }) 182 | return 183 | } 184 | 185 | func (replay *ReplayDB) SerilizeToFile() (filename string) { 186 | filename = "./single.json" 187 | outmap := replay.GetJSONMap() 188 | 189 | result := map[string]interface{}{ 190 | "paths": outmap, 191 | } 192 | 193 | jsonStr, err := json.MarshalIndent(result, "", " ") 194 | 195 | err = ioutil.WriteFile(filename, jsonStr, 0666) 196 | if err != nil { 197 | log.Println(err) 198 | } 199 | return 200 | } 201 | -------------------------------------------------------------------------------- /getversion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | version=`git log --date=iso --pretty=format:"%cd @%h" -1` 3 | if [ $? -ne 0 ]; then 4 | version="not a git repo" 5 | fi 6 | 7 | compile=`date +"%F %T %z"`" by "`go version` 8 | 9 | cat << EOF | gofmt > version.go 10 | package main 11 | 12 | const ( 13 | Version = "$version" 14 | Compile = "$compile" 15 | ) 16 | EOF 17 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/compasses/MockXServer/middleware" 7 | ) 8 | 9 | const banner string = ` 10 | 11 | Mock Server 12 | 13 | ` 14 | 15 | func main() { 16 | log.Println(banner) 17 | log.Printf("Git commit:%s\n", Version) 18 | log.Printf("Build time:%s\n", Compile) 19 | middle := middleware.NewMiddleware() 20 | middle.Run() 21 | } 22 | -------------------------------------------------------------------------------- /middleware/middlehandler.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "os" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | func (middleware *middleWare) SaveNotFound(path, method, reqBody, respBody string, statusCode int) (err error) { 16 | if err != nil { 17 | log.Println("JSON Normalize error ", err) 18 | } 19 | 20 | filename := strings.Replace(path+"Incomplete.json", "/", "_", -1) 21 | filename = "./input/" + filename 22 | 23 | result := map[string]interface{}{ 24 | path: map[string]interface{}{ 25 | method: map[string]interface{}{ 26 | "request": reqBody, 27 | "response": respBody, 28 | }, 29 | }, 30 | } 31 | 32 | jsonStr, err := json.MarshalIndent(result, "", " ") 33 | 34 | err = ioutil.WriteFile(filename, jsonStr, 0666) 35 | if err != nil { 36 | log.Println(err) 37 | } 38 | return nil 39 | } 40 | 41 | func (middleware *middleWare) returnFile(w http.ResponseWriter, filename, attachName string) { 42 | f, err := os.Open(filename) 43 | if err != nil { 44 | w.WriteHeader(500) 45 | w.Write([]byte(err.Error())) 46 | return 47 | } 48 | 49 | fileInfo, err := f.Stat() 50 | if err != nil { 51 | w.WriteHeader(500) 52 | w.Write([]byte(err.Error())) 53 | return 54 | } 55 | w.Header().Set("Content-Type", "application/octet-stream") 56 | w.Header().Set("Content-Disposition", `attachment; filename=`+attachName) 57 | w.Header().Set("Content-Length", strconv.Itoa(int(fileInfo.Size()))) 58 | io.Copy(w, f) 59 | } 60 | 61 | func (middleware *middleWare) TestTruncate() { 62 | dbFile := middleware.replaydb.GetDBFilePath() 63 | middleware.replaydb.Close() 64 | fmt.Println("DB file ", dbFile) 65 | db, err := os.OpenFile(dbFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) 66 | if err != nil { 67 | fmt.Println("OPen error ", err) 68 | } 69 | err = db.Truncate(0) 70 | if err != nil { 71 | fmt.Println("Truncate error ", err) 72 | } 73 | } 74 | 75 | func (middleware *middleWare) Truncate(w http.ResponseWriter) { 76 | dbFile := middleware.replaydb.GetDBFilePath() 77 | middleware.replaydb.Close() 78 | db, err := os.OpenFile(dbFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm) 79 | if err != nil { 80 | w.WriteHeader(500) 81 | w.Write([]byte(err.Error())) 82 | log.Println("OPen error ", err) 83 | } 84 | err = db.Truncate(0) 85 | if err != nil { 86 | w.WriteHeader(500) 87 | w.Write([]byte(err.Error())) 88 | log.Println("Truncate error ", err) 89 | } 90 | w.WriteHeader(200) 91 | w.Write([]byte("

Truncate successful

")) 92 | } 93 | 94 | func (middleware *middleWare) TestPactGen() { 95 | middleware.replaydb.ReadDir("./input") 96 | middleware.GenPactWithProvider() 97 | } 98 | 99 | func (middleware *middleWare) GenerateJSON(w http.ResponseWriter) { 100 | middleware.replaydb.ReadDir("./input") 101 | filename := middleware.replaydb.SerilizeToFile() 102 | middleware.returnFile(w, filename, "JSONFile.json") 103 | } 104 | 105 | func (middleware *middleWare) GeneratePACT(w http.ResponseWriter) { 106 | middleware.replaydb.ReadDir("./input") 107 | middleware.GenPactWithProvider() 108 | pactfile := middleware.GetPactFile() 109 | if len(pactfile) > 0 { 110 | middleware.returnFile(w, pactfile, "ConsumerContracts.json") 111 | } else { 112 | log.Println("No Pact File Generate ") 113 | w.Write([]byte("No Pact File Generate ")) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/compasses/MockXServer/db" 15 | "github.com/compasses/MockXServer/offline" 16 | "github.com/compasses/MockXServer/online" 17 | "github.com/compasses/MockXServer/utils" 18 | ) 19 | 20 | type config struct { 21 | RunMode string 22 | TLS string 23 | RemoteServer string 24 | ListenOn string 25 | OfflineHandler string 26 | LogFile string 27 | } 28 | 29 | func GetConfiguration() (conf *config, err error) { 30 | //get configuration 31 | file, err := os.Open("./config.json") 32 | if err != nil { 33 | log.Println("read file failed...", err) 34 | log.Println("Just run in offline mode") 35 | return nil, err 36 | } 37 | 38 | data, err := ioutil.ReadAll(file) 39 | if err != nil { 40 | log.Println("read file failed...", err) 41 | log.Println("Just run in offline mode") 42 | return nil, err 43 | } else { 44 | json.Unmarshal(data, &conf) 45 | log.Println("get configuration:", string(data)) 46 | } 47 | 48 | return conf, nil 49 | } 50 | 51 | type middleWare struct { 52 | conf *config 53 | handler http.Handler 54 | replaydb *db.ReplayDB 55 | runmode int 56 | } 57 | 58 | func NewMiddleware() *middleWare { 59 | conf, err := GetConfiguration() 60 | if err != nil { 61 | return nil 62 | } 63 | // set log format 64 | if len(conf.LogFile) > 0 { 65 | f, err := os.OpenFile(conf.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 66 | if err != nil { 67 | log.Println("error opening file: %v", err) 68 | } 69 | 70 | log.SetOutput(f) 71 | go func(f *os.File) { 72 | for { 73 | f.Sync() 74 | time.Sleep(time.Second) 75 | } 76 | }(f) 77 | } else { 78 | log.Println("Not assign log file, just print to command window.") 79 | } 80 | log.SetFlags(log.LstdFlags | log.Lshortfile | log.Lmicroseconds) 81 | 82 | middleware := new(middleWare) 83 | middleware.conf = conf 84 | dbreplay, err := db.NewReplayDB("./ReplayDB") 85 | dbreplay.ReadDir("./input") 86 | if err != nil { 87 | log.Println("Open replayDB error ", err) 88 | return nil 89 | } 90 | middleware.replaydb = dbreplay 91 | 92 | if conf.RunMode == "offline" { 93 | log.Println("MockXServer Run in offline mode...") 94 | offlineDB, err := db.NewReplayDB("./OfflineDB") 95 | if err != nil { 96 | log.Println("Open OfflineDB error ", err) 97 | return nil 98 | } 99 | 100 | middleware.handler = offline.NewServerRouter(offlineDB.GetDB()) 101 | middleware.runmode = 0 102 | } else { 103 | log.Println("MockXServer Run in online mode...") 104 | middleware.handler = online.NewProxyHandler(conf.RemoteServer, middleware.replaydb) 105 | middleware.runmode = 1 106 | } 107 | 108 | return middleware 109 | } 110 | 111 | func (middleware *middleWare) PreHandle(w http.ResponseWriter, req *http.Request) bool { 112 | path := strings.Split(req.RequestURI, "?") 113 | log.Println("prehandle path: ", req.RequestURI) 114 | 115 | if path[0] == "/json" { 116 | middleware.GenerateJSON(w) 117 | return true 118 | } else if path[0] == "/pact" { 119 | middleware.GeneratePACT(w) 120 | return true 121 | } else if path[0] == "/truncate" { 122 | middleware.Truncate(w) 123 | return true 124 | } 125 | 126 | return false 127 | } 128 | 129 | func (middle *middleWare) Run() { 130 | log.Println("Listen ON: ", middle.conf.ListenOn) 131 | if middle.conf.TLS == "on" { 132 | log.Fatal(http.ListenAndServeTLS(middle.conf.ListenOn, "cert.pem", "key.pem", middle)) 133 | } else { 134 | log.Fatal(http.ListenAndServe(middle.conf.ListenOn, middle)) 135 | } 136 | } 137 | 138 | func (middleware *middleWare) HandleOffline(w http.ResponseWriter, req *http.Request) { 139 | newbody := make([]byte, req.ContentLength) 140 | req.Body.Read(newbody) 141 | log.Println("offline handle , try to get ", req.RequestURI, req.Method, string(newbody)) 142 | 143 | res, err := middleware.replaydb.GetResponse(req.RequestURI, req.Method, string(newbody)) 144 | if err != nil || res == nil { 145 | log.Println("Cannot get response from replaydb on offline mode", err) 146 | if middleware.conf.OfflineHandler != "off" { 147 | log.Println("Need handle request in offline handler") 148 | middleware.SaveNotFound(req.RequestURI, req.Method, string(newbody), "...xxx...", 200) 149 | 150 | newRq, err := http.NewRequest(req.Method, req.RequestURI, ioutil.NopCloser(bytes.NewReader(newbody))) 151 | if err != nil { 152 | log.Println("new http request failed ", err) 153 | } 154 | utils.RequstFormat(true, newRq, string(newbody)) 155 | middleware.handler.ServeHTTP(w, newRq) 156 | } 157 | } else { 158 | result, _ := utils.TOJsonInterface(res) 159 | log.Println("Get response from replaydb on offline mode ", (result)) 160 | resultmap := result.(map[string]interface{}) 161 | for key, value := range resultmap { 162 | status, _ := strconv.Atoi(key) 163 | w.WriteHeader(status) 164 | stream := []byte(value.(string)) 165 | _, err = w.Write(stream) 166 | if err != nil { 167 | log.Println("Get response from replaydb but write error ", err) 168 | } 169 | break 170 | } 171 | } 172 | } 173 | 174 | func (middleware *middleWare) ServeHTTP(w http.ResponseWriter, req *http.Request) { 175 | w.Header().Set("Content-Type", "application/json") 176 | preHandled := middleware.PreHandle(w, req) 177 | if preHandled { 178 | return 179 | } 180 | 181 | if middleware.conf.RunMode == "online" { 182 | middleware.handler.ServeHTTP(w, req) 183 | } else { 184 | // firstly try to get from replaydb 185 | middleware.HandleOffline(w, req) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /middleware/pactgenerate.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "strconv" 10 | 11 | "github.com/SEEK-Jobs/pact-go" 12 | "github.com/SEEK-Jobs/pact-go/provider" 13 | "github.com/compasses/MockXServer/utils" 14 | ) 15 | 16 | const PactsDir = "./pacts" 17 | 18 | type ProviderAPIClient struct { 19 | baseURL string 20 | } 21 | 22 | func (c *ProviderAPIClient) ClientRun(method, path string, reqBody interface{}) error { 23 | url := fmt.Sprintf("%s%s", c.baseURL, path) 24 | reqb := utils.JsonInterfaceToByte(reqBody) 25 | 26 | log.Println("going to verify: ", url+" "+method) 27 | 28 | req, err := http.NewRequest(method, url, bytes.NewBuffer(reqb)) 29 | if err != nil { 30 | log.Println(err) 31 | return err 32 | } 33 | 34 | client := &http.Client{} 35 | resp, err := client.Do(req) 36 | 37 | if err != nil { 38 | log.Println(err) 39 | return err 40 | } 41 | defer resp.Body.Close() 42 | 43 | res, err := ioutil.ReadAll(resp.Body) 44 | if err != nil { 45 | log.Println("ioutil read err ", err, " body", string(res)) 46 | return err 47 | } 48 | //log.Println("Got response: ", string(res)) 49 | 50 | return nil 51 | } 52 | 53 | func (middleware *middleWare) GetPactFile() string { 54 | files, err := ioutil.ReadDir(PactsDir) 55 | if err != nil { 56 | log.Println(err) 57 | return "" 58 | } 59 | //now just run the first file 60 | for _, file := range files { 61 | log.Println("upload pact file name ", file.Name()) 62 | return PactsDir + "/" + file.Name() 63 | } 64 | return "" 65 | } 66 | 67 | func (middleware *middleWare) buildPact(consumerName, providerName string) pact.Builder { 68 | return pact. 69 | NewConsumerPactBuilder(&pact.BuilderConfig{PactPath: PactsDir}). 70 | ServiceConsumer(consumerName). 71 | HasPactWith(providerName) 72 | } 73 | 74 | func (middleware *middleWare) RunPact(builder pact.Builder, path, method string, reqBody, respBody interface{}, statusCode int, 75 | consumerName, providerName string) { 76 | ms, msUrl := builder.GetMockProviderService() 77 | 78 | request := provider.NewJSONRequest(method, path, "", nil) 79 | if reqBody != nil { 80 | err := request.SetBody(reqBody) 81 | if err != nil { 82 | log.Println("Set request error ", err, " reqBody ", respBody) 83 | } 84 | } 85 | 86 | header := make(http.Header) 87 | header.Add("content-type", "application/json") 88 | response := provider.NewJSONResponse(statusCode, header) 89 | if respBody != nil { 90 | err := response.SetBody(respBody) 91 | if err != nil { 92 | log.Println("Set Response error ", err, " respBody ", respBody) 93 | } 94 | } 95 | 96 | //Register interaction for this test scope 97 | if err := ms.Given(consumerName). 98 | UponReceiving(providerName). 99 | With(*request). 100 | WillRespondWith(*response); err != nil { 101 | log.Println(err) 102 | } 103 | 104 | //log.Println("Register: ", " Request ", string(req), " response ", respBody) 105 | 106 | //test 107 | client := &ProviderAPIClient{baseURL: msUrl} 108 | if err := client.ClientRun(method, path, reqBody); err != nil { 109 | log.Println(err) 110 | } 111 | 112 | //Verify registered interaction 113 | if err := ms.VerifyInteractions(); err != nil { 114 | log.Println(err) 115 | } 116 | 117 | //Clear interaction for this test scope, if you need to register and verify another interaction for another test scope 118 | ms.ClearInteractions() 119 | } 120 | 121 | func (middleware *middleWare) GenPactWithProvider() { 122 | builder := middleware.buildPact("EShop Online Store", "EShop Adaptor") 123 | //map[string]map[string][]interface{} 124 | //"Path", "Method", "[req..., rsp...,]" 125 | interactMap := middleware.replaydb.GetJSONMap() 126 | for path, value := range interactMap { 127 | for method, interacts := range value { 128 | var count int = 0 129 | for _, detailMapel := range interacts { 130 | detailMapItem := detailMapel.(map[string]interface{}) 131 | request, ok := detailMapItem["request"] 132 | if !ok { 133 | log.Println("missing request, continue ", detailMapItem) 134 | continue 135 | } 136 | respose, ok := detailMapItem["response"] 137 | if !ok { 138 | log.Println("missing response, continue ", detailMapItem) 139 | continue 140 | } 141 | responseMap := respose.(map[string]interface{}) 142 | count++ 143 | for k, v := range responseMap { 144 | status, _ := strconv.Atoi(k) 145 | //fmt.Println("\r\nstore:", request, "response", v) 146 | consumName := "mock server for " + path + " method " + method + " " + strconv.Itoa(count) 147 | provideName := "pact contract for " + path + " method " + method + " " + strconv.Itoa(count) 148 | middleware.RunPact(builder, path, method, request, v, status, consumName, 149 | provideName) 150 | count++ 151 | break 152 | } 153 | } 154 | } 155 | } 156 | 157 | //Finally, build to produce the pact json file 158 | if err := builder.Build(); err != nil { 159 | log.Println(err) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /middleware/test.json: -------------------------------------------------------------------------------- 1 | http://localhost:8080/sbo/api/v1/ChannelAccounts?filter=(account eq 'sss@ss2.com') and (salesChannel.id eq 2) 2 | 3 | [ 4 | { 5 | "id": 1, 6 | "customerId": 2, 7 | "salesChannel": { 8 | "id": 3, 9 | "name": "YZ" 10 | }, 11 | "contactId": null, 12 | "lastPurchase": "2015-09-21T22:10:00.000Z", 13 | "account": "sss@ss2.com", 14 | "resetPasswordLink": null, 15 | "unifiedAccount": "sss@ss2.com" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /offline/logger.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/julienschmidt/httprouter" 9 | ) 10 | 11 | func Logger(inner httprouter.Handle, name string) httprouter.Handle { 12 | return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 13 | log.Printf( 14 | "Start to handle : %s\t%s\t%s\t", 15 | r.Method, 16 | r.RequestURI, 17 | name, 18 | ) 19 | start := time.Now() 20 | //time.Sleep(time.Millisecond * 1000) 21 | 22 | inner(w, r, ps) 23 | 24 | log.Printf( 25 | "%s\t%s\t%s\t%s", 26 | r.Method, 27 | r.RequestURI, 28 | name, 29 | time.Since(start), 30 | ) 31 | }) 32 | } 33 | 34 | func LoggerNotFound(inner http.HandlerFunc) http.HandlerFunc { 35 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 | start := time.Now() 37 | 38 | inner.ServeHTTP(w, r) 39 | 40 | log.Printf( 41 | "%s\t%s\t%s\t%s", 42 | r.Method, 43 | r.RequestURI, 44 | "404 Not Found", 45 | time.Since(start), 46 | ) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /offline/modals.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import . "github.com/compasses/MockXServer/utils" 4 | 5 | type ATSReq struct { 6 | SkuIds IDSeqs 7 | ChanelId TableId 8 | } 9 | 10 | type ATSRsp struct { 11 | SkuId TableId `json:"skuId"` 12 | Ats int64 `json:"ats"` 13 | AllowBackOrder bool `json:"allowBackOrder"` 14 | } 15 | 16 | type RecommandInfo struct { 17 | ChannelId int 18 | ProductId TableId `json:",string"` 19 | CurrencyId int 20 | } 21 | 22 | type Customer struct { 23 | Id TableId `json:"id,string"` 24 | CustomerType string `json:"customerType"` 25 | Email string `json:"email"` 26 | } 27 | 28 | type CustomerCreateRsp struct { 29 | CustomerCode string `json:"customerCode"` 30 | CustomerID TableId `json:"customerID"` 31 | ChannelAccountID TableId `json:"channelAccountID"` 32 | //FailType string `json:"failType"` 33 | } 34 | 35 | type CustomerAddress struct { 36 | Id TableId `json:"id"` 37 | CustomerInfo Customer `json:"customer"` 38 | AddressInfo interface{} `json:"address"` 39 | DefaultBillTo bool `json:"defaultBillTo"` 40 | DefaultShipTo bool `json:"defaultShipTo"` 41 | } 42 | 43 | type CustomerCreate struct { 44 | ChannelId TableId 45 | Account string 46 | Customer Customer 47 | CustomerType string 48 | AccountInfo CustomerCreateRsp 49 | Addresses []CustomerAddress 50 | } 51 | 52 | type CartItems struct { 53 | SkuId interface{} `json:"skuId"` 54 | UnitPrice interface{} `json:"unitPrice"` 55 | Quantity interface{} `json:"quantity"` 56 | TaxAmount interface{} `json:"taxAmount"` 57 | DiscountPercentage interface{} `json:"discountPercentage"` 58 | LineTotal interface{} `json:"lineTotal"` 59 | LineTotalAfterDisc interface{} `json:"lineTotalAfterDisc"` 60 | StandardPrice interface{} `json:"standardPrice"` 61 | Remark interface{} `json:"remark"` 62 | } 63 | 64 | type ShoppingCart struct { 65 | CartTotal interface{} `json:"cartTotal"` 66 | DiscountPercentage interface{} `json:"discountPercentage"` 67 | DiscountSum interface{} `json:"discountSum"` 68 | PriceMethod interface{} `json:"priceMethod"` 69 | CartItems []CartItems `json:"cartItems"` 70 | } 71 | 72 | type CheckoutCartPlayLoad struct { 73 | ShippingAddress interface{} `json:"shippingAddress"` 74 | BillingAddress interface{} `json:"billingAddress"` 75 | CustomerId interface{} `json:"customerId"` 76 | ChannelAccountId interface{} `json:"channelAccountId"` 77 | ChannelId interface{} `json:"channelId"` 78 | ShoppingCart ShoppingCart `json:"shoppingCart"` 79 | ShippingMethod interface{} `json:"shippingMethod"` 80 | Promotion interface{} `json:"promotion"` 81 | TaxTotal interface{} `json:"taxTotal"` 82 | OrderTotal interface{} `json:"orderTotal"` 83 | DiscountPercentage interface{} `json:"discountPercentage"` 84 | DiscountSum interface{} `json:"discountSum"` 85 | } 86 | 87 | type CheckoutShoppingCart struct { 88 | ShoppingCart CheckoutCartPlayLoad `json:"shoppingCart"` 89 | } 90 | 91 | type CheckoutShoppingCartRsp struct { 92 | CheckoutCartPlayLoad 93 | ShippingCosts interface{} `json:"shippingCosts"` 94 | EnableExpressDelivery bool `json:"enableExpressDelivery"` 95 | } 96 | 97 | type OrderCreate struct { 98 | EShopOrder CheckoutCartPlayLoad `json:"eShopOrder"` 99 | } 100 | -------------------------------------------------------------------------------- /offline/repo.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "strconv" 7 | 8 | "github.com/boltdb/bolt" 9 | . "github.com/compasses/MockXServer/utils" 10 | ) 11 | 12 | var GlobalDB *bolt.DB 13 | 14 | //tables define 15 | const ( 16 | ProductTable = "PRODUCTS" 17 | SKUTable = "PRODUCT-SKU" 18 | DefaultATS TableId = 10 19 | CustomerTable = "CUSTOMER" 20 | AddressTable = "ADDRESS" 21 | OrderTable = "ORDERS" 22 | RecommandProductsNum = 15 23 | ) 24 | 25 | func RepoCheckoutShoppingCart(checkoutCart CheckoutCartPlayLoad) CheckoutShoppingCartRsp { 26 | result := CheckoutShoppingCartRsp{checkoutCart, nil, true} 27 | 28 | var cartTotal float64 29 | for i, val := range result.ShoppingCart.CartItems { 30 | var quantity float64 31 | switch v := val.Quantity.(type) { 32 | case string: 33 | quantity = ToFloat64FromString(v) 34 | default: 35 | quantity = float64(v.(float64)) 36 | } 37 | 38 | price := ToFloat64FromString(val.UnitPrice.(string)) 39 | result.ShoppingCart.CartItems[i].LineTotal = (float64)(quantity) * price 40 | cartTotal += result.ShoppingCart.CartItems[i].LineTotal.(float64) 41 | } 42 | 43 | result.ShoppingCart.CartTotal = cartTotal 44 | result.OrderTotal = cartTotal 45 | result.TaxTotal = cartTotal 46 | 47 | result.ShippingCosts = []map[string]interface{}{ 48 | { 49 | "default": true, 50 | "identifier": "FIX-100-100", 51 | "type": "FIX", 52 | "id": -100, 53 | "carrierId": -100, 54 | "name": "EShopDefaultRate", 55 | "cost": "0.0", 56 | "minDeliveryDays": 2, 57 | "maxDeliveryDays": 5, 58 | }, 59 | } 60 | return result 61 | } 62 | 63 | func RepoGetSalesOrders(channelAccountId string) (result interface{}) { 64 | 65 | GlobalDB.Update(func(tx *bolt.Tx) error { 66 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable)) 67 | if err != nil { 68 | HandleError(err) 69 | return err 70 | } 71 | 72 | cusBuk := orderBucket.Bucket([]byte(channelAccountId)) 73 | if cusBuk == nil { 74 | log.Println("not found this user " + channelAccountId) 75 | return nil 76 | } 77 | c := cusBuk.Cursor() 78 | var number int 79 | var orders []interface{} 80 | 81 | for k, v := c.First(); k != nil; k, v = c.Next() { 82 | var temp interface{} 83 | json.Unmarshal(v, &temp) 84 | orders = append(orders, temp) 85 | number++ 86 | } 87 | //reverse the orders 88 | for i := len(orders)/2 - 1; i >= 0; i-- { 89 | opp := len(orders) - 1 - i 90 | orders[i], orders[opp] = orders[opp], orders[i] 91 | } 92 | 93 | result = map[string]interface{}{ 94 | "value": orders, 95 | "odata.count": number, 96 | } 97 | return nil 98 | }) 99 | return 100 | } 101 | 102 | func RepoGetSalesOrder(orderId TableId, channelAccountId string) (result interface{}) { 103 | 104 | GlobalDB.Update(func(tx *bolt.Tx) error { 105 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable)) 106 | if err != nil { 107 | HandleError(err) 108 | return err 109 | } 110 | 111 | cusBuk := orderBucket.Bucket([]byte(channelAccountId)) 112 | if cusBuk == nil { 113 | result = "not found this user " + channelAccountId 114 | return nil 115 | } 116 | 117 | orderBytes := cusBuk.Get(orderId.ToBytes()) 118 | 119 | json.Unmarshal(orderBytes, &result) 120 | 121 | return nil 122 | }) 123 | return 124 | } 125 | 126 | func GenerateOrder(order CheckoutCartPlayLoad, orderId uint64) interface{} { 127 | return map[string]interface{}{ 128 | "id": orderId, 129 | "docNumber": orderId, 130 | "billingAddr": order.BillingAddress, 131 | "shippingAddr": order.ShippingAddress, 132 | "shippingCost": "0", 133 | "subTotal": "500", 134 | "grossDocTotal": "500", 135 | "taxTotal": "0", 136 | "customer": map[string]interface{}{ 137 | "id": order.CustomerId, 138 | }, 139 | "process": map[string]interface{}{ 140 | "id": 3, 141 | "processName": "快递", 142 | }, 143 | "salesOrderLines": []map[string]interface{}{ 144 | { 145 | "amazonOrderItemCode": nil, 146 | "baseDocId": nil, 147 | "baseDocLineId": nil, 148 | "baseDocLineNumber": nil, 149 | "baseDocNumber": nil, 150 | "baseDocType": nil, 151 | "canelReason": nil, 152 | "costTotal": "0", 153 | "discountPercentage": "0", 154 | "docCurrencyId": 1, 155 | "exceptionFlag": false, 156 | "exceptionReasonDesc": nil, 157 | "giftMessage": nil, 158 | "grossLineTotal": "500.00", 159 | "grossLineTotalAfterDisc": "500.00", 160 | "grossLineTotalAfterDiscLC": "500.00", 161 | "grossLineTotalLC": "500.00", 162 | "grossProfitAmount": "0", 163 | "grossProfitRate": "0", 164 | "grossUnitPrice": "500.00", 165 | "id": 119, 166 | "inventoryUomName": "Unit", 167 | "inventoryUomQuantity": "1.00", 168 | "invoiceStatus": "tNotInvoiced", 169 | "isNonLogistical": false, 170 | "isPreparingStock": false, 171 | "isPromotionAppliable": true, 172 | "isService": false, 173 | "lineAction": "C,A,I,S", 174 | "lineCalcBase": "byTotal", 175 | "lineComments": nil, 176 | "lineNumber": 1, 177 | "lineType": "tProductLine", 178 | "logisticsStatus": "tAllocated", 179 | "merchantFulfillmentItemID": nil, 180 | "netLineTotal": "500.00", 181 | "netLineTotalAfterDisc": "500.00", 182 | "netLineTotalAfterDiscLC": "500.00", 183 | "netLineTotalLC": "500.00", 184 | "netUnitPrice": "500.00", 185 | "originLine": nil, 186 | "originSkuPrice": "500", 187 | "planningWhsId": nil, 188 | "priceSource": nil, 189 | "promotionDescription": nil, 190 | "promotionHintBenefit": nil, 191 | "promotionHintDes": nil, 192 | "propertyDynamicMeta": "grossLineTotalAfterDiscLC:T,grossLineTotalLC:T,salesUomName:T,grossLineTotalAfterDisc:T,promotionItem:T,grossProfitRate:T,netLineTotalLC:T,taxAmountLC:T,grossProfitAmount:T,salesUom:T,lineType:T,exceptionReasonDesc:T,inventoryUomQuantity:T,exceptionReason:T,originSkuPrice:T,netLineTotalAfterDisc:T,netLineTotalAfterDiscLC:T,inventoryUom:T,totalLC:T,exceptionFlag:T,totalAfterDiscountLC:T,costTotal:T,invoiceStatus:T,lineNumber:T,inventoryUomName:T,taxAmount:T,promotion:T", 193 | "purchaseOrderId": nil, 194 | "purchaseOrderLineId": nil, 195 | "quantity": "1", 196 | "remark": nil, 197 | "salesUomName": "Unit", 198 | "shippingId": nil, 199 | "shippingType": nil, 200 | "skuCode": "ProductCode13", 201 | "skuMainLogoURL": "http://internal-ci.s3.amazonaws.com/T1/2015/11/09/b09fb2bc-10f8-48fd-a4e1-e190194992a4", 202 | "skuName": "Red Silk Dress", 203 | "targetDocId": nil, 204 | "taxAmount": "0", 205 | "taxAmountLC": "0", 206 | "total": "500.00", 207 | "totalAfterDiscount": "500.00", 208 | "totalAfterDiscountLC": "500.00", 209 | "totalLC": "500.00", 210 | "unitCost": "0", 211 | "unitPrice": "500.00", 212 | "uomConversionRate": "1", 213 | "variantValues": "", 214 | "warehouseCode": "主仓库", 215 | "warehouseName": "主仓库", 216 | "docCurrency": nil, 217 | "exceptionReason": nil, 218 | "inventoryUom": map[string]interface{}{ 219 | "description": "无", 220 | "id": 1, 221 | "name": "无", 222 | }, 223 | "promotion": nil, 224 | "promotionItem": nil, 225 | "salesUom": map[string]interface{}{ 226 | "description": "无", 227 | "id": 1, 228 | "name": "无", 229 | }, 230 | "sku": map[string]interface{}{ 231 | "code": "ProductCode13", 232 | "id": 57, 233 | "name": "Red Silk Dress", 234 | "product": nil, 235 | }, 236 | "warehouse": map[string]interface{}{ 237 | "id": 1, 238 | "whsName": "主仓库", 239 | "virtualNodes": nil, 240 | "warehouseOperators": nil, 241 | "defaulterUser": nil, 242 | }, 243 | }, 244 | }} 245 | } 246 | 247 | func RepoCreateOrder(order CheckoutCartPlayLoad) interface{} { 248 | var newOrder interface{} 249 | 250 | GlobalDB.Update(func(tx *bolt.Tx) error { 251 | orderBucket, err := tx.CreateBucketIfNotExists([]byte(OrderTable)) 252 | if err != nil { 253 | HandleError(err) 254 | return err 255 | } 256 | var cusOrderBuck *bolt.Bucket 257 | if order.ChannelAccountId == nil { 258 | cusOrderBuck, err = orderBucket.CreateBucketIfNotExists([]byte("GUESTUSER")) 259 | } else { 260 | cusOrderBuck, err = orderBucket.CreateBucketIfNotExists([]byte(order.ChannelAccountId.(string))) 261 | } 262 | if err != nil { 263 | HandleError(err) 264 | return err 265 | } 266 | newId, _ := orderBucket.NextSequence() 267 | newOrder = GenerateOrder(order, newId) 268 | 269 | orderBytes, _ := json.Marshal(newOrder) 270 | cusOrderBuck.Put(TableId(newId).ToBytes(), orderBytes) 271 | 272 | return nil 273 | }) 274 | 275 | return newOrder 276 | } 277 | 278 | func GetProductATS(ProductId TableId) int64 { 279 | var atsQua int64 280 | log.Println("Try to process ats ", GlobalDB) 281 | 282 | GlobalDB.Update(func(tx *bolt.Tx) error { 283 | b, err := tx.CreateBucketIfNotExists([]byte(SKUTable)) 284 | 285 | if err != nil { 286 | HandleError(err) 287 | return err 288 | } 289 | 290 | bb, errbb := b.CreateBucketIfNotExists(ProductId.ToBytes()) 291 | 292 | if errbb != nil { 293 | HandleError(errbb) 294 | return err 295 | } 296 | 297 | ats := bb.Get([]byte("ats")) 298 | if ats == nil { 299 | //new product id, need initialize the ats info 300 | bb.Put([]byte("ats"), DefaultATS.ToBytes()) 301 | atsQua = int64(DefaultATS) 302 | } else { 303 | result := ToInt64FromBytes(ats) 304 | if err != nil { 305 | HandleError(err) 306 | return err 307 | } 308 | atsQua = result 309 | } 310 | return nil 311 | }) 312 | return atsQua 313 | } 314 | 315 | func RepoCreateATSRsp(req *ATSReq) []ATSRsp { 316 | var rest []ATSRsp 317 | 318 | for _, atsR := range req.SkuIds { 319 | 320 | atsQua := GetProductATS(atsR) 321 | rsp := ATSRsp{ 322 | SkuId: atsR, 323 | Ats: atsQua, 324 | AllowBackOrder: true, 325 | } 326 | rest = append(rest, rsp) 327 | } 328 | log.Printf("ATS Rsp %+v\n", rest) 329 | return rest 330 | } 331 | 332 | func RepoCreateRecommandationProducts(Id TableId) []TableId { 333 | var ProductId []TableId 334 | 335 | GlobalDB.Update(func(tx *bolt.Tx) error { 336 | b, err := tx.CreateBucketIfNotExists([]byte(ProductTable)) 337 | if err != nil { 338 | HandleError(err) 339 | return err 340 | } 341 | 342 | c := b.Cursor() 343 | num := 0 344 | 345 | for k, _ := c.First(); k != nil && (num < RecommandProductsNum); k, _ = c.Next() { 346 | ProductId = append(ProductId, TableId(ToInt64FromBytes(k))) 347 | num++ 348 | } 349 | _, errbb := b.CreateBucketIfNotExists(Id.ToBytes()) 350 | 351 | if errbb != nil { 352 | HandleError(errbb) 353 | return err 354 | } 355 | 356 | return nil 357 | }) 358 | 359 | return ProductId 360 | } 361 | 362 | func RepoGetCustomer(channelAccountId TableId) interface{} { 363 | var result CustomerCreate 364 | key := []byte(CustomerTable) 365 | 366 | GlobalDB.Update(func(tx *bolt.Tx) error { 367 | pb, err := tx.CreateBucketIfNotExists(key) 368 | 369 | if err != nil { 370 | HandleError(err) 371 | return err 372 | } 373 | 374 | account := pb.Get(channelAccountId.ToBytes()) 375 | if account == nil { 376 | HandleError(err) 377 | return err 378 | } 379 | 380 | json.Unmarshal(account, &result) 381 | return nil 382 | }) 383 | 384 | return map[string]interface{}{ 385 | "id": result.AccountInfo.CustomerID, 386 | "displayName": "Jet He", 387 | "email": "update@customer.com", //result.Account, 388 | "checkDuplication": true, 389 | "creationTime": "2015-11-17T05:56:58.812Z", 390 | "creatorDisplayName": "ERP SUITE", 391 | "creditLimit": "250000", 392 | "creditBalance": "100000", 393 | "outstandingPayment": "300000", 394 | "customerCode": "3", 395 | "customerName": "Jet Jet", 396 | "customerType": "CORPORATE_CUSTOMER", 397 | "dateOfBirth": nil, 398 | "facebookAccount": nil, 399 | "fax": "22323232", 400 | "firstName": "GG", 401 | "gender": nil, 402 | "googleAccount": nil, 403 | "lastMarketingCampaign": nil, 404 | "lastName": "HH", 405 | "linkedINAccount": nil, 406 | "mainContact": nil, 407 | "marketingStatus": "Unknown", 408 | "membershipBalance": 0, 409 | "membershipId": nil, 410 | "membershipSwitchOn": false, 411 | "membershipTotalEarn": 0, 412 | "mobile": 1588888888, 413 | "ownerCode": 1, 414 | "ownerDisplayName": "ERP SUITE", 415 | "phone": "12121212", 416 | "portraitId": 0, 417 | "position": nil, 418 | "remarks": "Good job", 419 | "resetPasswordLink": nil, 420 | "socialImageUrl": nil, 421 | "stage": "SUSPECT", 422 | "status": "ACTIVE", 423 | "targetGroup": nil, 424 | "taxType": "LIABLE", 425 | "title": nil, 426 | "twitterAccount": nil, 427 | "twitterDisplayName": nil, 428 | "updateTime": "2015-11-17T05:56:58.812Z", 429 | "updatorDisplayName": "ERP SUITE", 430 | "vatRegistrationNumber": nil, 431 | "versionNum": 0, 432 | "webSite": nil, 433 | "weiboDisplayName": nil, 434 | "ext_default_UDF5": "11", 435 | "priceList": nil, 436 | "customerGroup": nil, 437 | "defaultBillToAddress": nil, 438 | "defaultShipToAddress": nil, 439 | "industry": nil, 440 | "language": nil, 441 | "linkedCustomer": map[string]interface{}{ 442 | "displayName": "Jet Jet", 443 | "id": result.AccountInfo.CustomerID, 444 | "priceList": nil, 445 | "customerGroup": nil, 446 | "defaultBillToAddress": nil, 447 | "defaultShipToAddress": nil, 448 | "industry": nil, 449 | "language": nil, 450 | "linkedCustomer": nil, 451 | "membershipLevel": nil, 452 | "paymentAccount": nil, 453 | "paymentTerm": nil, 454 | "serviceLevelPlan": nil, 455 | "twitter": nil, 456 | "weibo": nil, 457 | }, 458 | "membershipLevel": map[string]interface{}{ 459 | "id": 1, 460 | "name": "普通会员", 461 | }, 462 | "paymentAccount": map[string]interface{}{ 463 | "acctName": "Cash", 464 | "id": 1, 465 | "acctType": nil, 466 | "country": nil, 467 | }, 468 | "paymentTerm": map[string]interface{}{ 469 | "id": 1, 470 | "name": "现金基础", 471 | }, 472 | "serviceLevelPlan": map[string]interface{}{ 473 | "displayName": "Gold", 474 | "id": 1, 475 | }, 476 | "twitter": nil, 477 | "weibo": nil, 478 | } 479 | } 480 | 481 | func RepoCreateAccount(customer CustomerCreate) CustomerCreateRsp { 482 | var result CustomerCreateRsp 483 | key := []byte(CustomerTable) 484 | 485 | GlobalDB.Update(func(tx *bolt.Tx) error { 486 | pb, err := tx.CreateBucketIfNotExists(key) 487 | 488 | if err != nil { 489 | HandleError(err) 490 | return err 491 | } 492 | 493 | customerId, _ := pb.NextSequence() 494 | 495 | b, err := pb.CreateBucketIfNotExists([]byte(customer.Account)) 496 | 497 | if err != nil { 498 | HandleError(err) 499 | return err 500 | } 501 | 502 | user := b.Get([]byte("User")) 503 | if user == nil { 504 | //create new user 505 | customer.AccountInfo.CustomerID = TableId(customerId) 506 | customer.AccountInfo.CustomerCode = "offline" + strconv.FormatInt(customer.AccountInfo.CustomerID.ToInt(), 10) 507 | customer.AccountInfo.ChannelAccountID = TableId(customer.ChannelId * customer.AccountInfo.CustomerID) 508 | //test 509 | cusStream, _ := json.Marshal(&customer) 510 | b.Put([]byte("User"), cusStream) 511 | pb.Put(customer.AccountInfo.ChannelAccountID.ToBytes(), cusStream) 512 | //customer.AccountInfo.FailType = "CUSTOMERTYPEMISSMATCH" 513 | result = customer.AccountInfo 514 | // pbaddr, err := tx.CreateBucketIfNotExists([]byte(AddressTable)) 515 | // if err != nil { 516 | // HandleError(err) 517 | // return err 518 | // } 519 | // pbaddr.Put(customer.AccountInfo.CustomerID.ToBytes(), cusStream) 520 | } else { 521 | json.Unmarshal(user, &customer) 522 | result = customer.AccountInfo 523 | } 524 | 525 | return nil 526 | }) 527 | 528 | return result 529 | } 530 | 531 | func RepoCreateAddress(customer *CustomerAddress) (result interface{}) { 532 | key := []byte(AddressTable) 533 | 534 | GlobalDB.Update(func(tx *bolt.Tx) error { 535 | pb, err := tx.CreateBucketIfNotExists(key) 536 | 537 | if err != nil { 538 | HandleError(err) 539 | return err 540 | } 541 | 542 | paddr, err := pb.CreateBucketIfNotExists(customer.CustomerInfo.Id.ToBytes()) 543 | if err != nil { 544 | HandleError(err) 545 | return err 546 | } 547 | 548 | //create the bucket for store address info 549 | addressBucket, _ := paddr.CreateBucketIfNotExists([]byte("addresses")) 550 | addressId, _ := addressBucket.NextSequence() 551 | customer.Id = TableId(addressId) 552 | 553 | streamD, _ := json.Marshal(customer) 554 | addressBucket.Put(TableId(addressId).ToBytes(), streamD) 555 | result = customer 556 | 557 | return nil 558 | }) 559 | return 560 | } 561 | 562 | func RepoUpdateAddress(addressId TableId, customer *CustomerAddress) (result interface{}) { 563 | key := []byte(AddressTable) 564 | 565 | GlobalDB.Update(func(tx *bolt.Tx) error { 566 | pb, err := tx.CreateBucketIfNotExists(key) 567 | 568 | if err != nil { 569 | HandleError(err) 570 | return err 571 | } 572 | 573 | cusBuk := pb.Bucket(customer.CustomerInfo.Id.ToBytes()) 574 | if cusBuk == nil { 575 | result = "not found this user:" + customer.CustomerInfo.Id.ToString() 576 | return nil 577 | } else { 578 | addressBucket := cusBuk.Bucket([]byte("addresses")) 579 | if addressBucket == nil { 580 | result = "not found this account:" + customer.CustomerInfo.Email 581 | return nil 582 | } 583 | 584 | //create the bucket for store address info 585 | oldAddress := addressBucket.Get(addressId.ToBytes()) 586 | var oldCustomerAddr CustomerAddress 587 | json.Unmarshal(oldAddress, &oldCustomerAddr) 588 | 589 | //set to new one 590 | oldCustomerAddr.AddressInfo = customer.AddressInfo 591 | oldCustomerAddr.DefaultShipTo = customer.DefaultShipTo 592 | oldCustomerAddr.DefaultBillTo = customer.DefaultBillTo 593 | streamD, _ := json.Marshal(oldCustomerAddr) 594 | 595 | addressBucket.Put(TableId(addressId).ToBytes(), streamD) 596 | result = oldCustomerAddr 597 | } 598 | return nil 599 | }) 600 | return 601 | } 602 | 603 | func RepoGetCustomerAddress(customerId TableId) (result interface{}) { 604 | key := []byte(AddressTable) 605 | 606 | GlobalDB.Update(func(tx *bolt.Tx) error { 607 | pb, err := tx.CreateBucketIfNotExists(key) 608 | 609 | if err != nil { 610 | HandleError(err) 611 | return err 612 | } 613 | cusBuk := pb.Bucket(customerId.ToBytes()) 614 | if cusBuk == nil { 615 | result = "not found this user:" + customerId.ToString() 616 | return nil 617 | } else { 618 | 619 | countinfo := make(map[string]interface{}) 620 | var count int64 = 0 621 | 622 | //create the bucket for store address info 623 | addressBucket := cusBuk.Bucket([]byte("addresses")) 624 | if addressBucket == nil { 625 | countinfo["odata.count"] = 0 626 | result = countinfo 627 | } else { 628 | var bos []interface{} 629 | cur := addressBucket.Cursor() 630 | for k, v := cur.First(); k != nil; k, v = cur.Next() { 631 | count++ 632 | var Addr CustomerAddress 633 | json.Unmarshal(v, &Addr) 634 | bos = append(bos, Addr) 635 | } 636 | 637 | countinfo["odata.count"] = 150 638 | countinfo["value"] = bos 639 | result = countinfo 640 | } 641 | } 642 | return nil 643 | }) 644 | return 645 | } 646 | -------------------------------------------------------------------------------- /offline/repo_test.go: -------------------------------------------------------------------------------- 1 | package offline_test 2 | 3 | import ( 4 | "encoding/binary" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestIntSliceEncode(t *testing.T) { 10 | var result []byte 11 | result = strconv.AppendInt(result, 124, 10) 12 | if string(result) != "124" { 13 | t.Errorf("%v\n", result) 14 | } 15 | result = strconv.AppendInt(result, 12, 10) 16 | 17 | another := make([]byte, 14) 18 | nul := binary.PutVarint(another, 2) 19 | nul = binary.PutVarint(another[4:], 111111111) 20 | if nul < 0 { 21 | t.Error() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /offline/sample_handler.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net" 7 | "net/http" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/boltdb/bolt" 12 | . "github.com/compasses/MockXServer/utils" 13 | 14 | "github.com/julienschmidt/httprouter" 15 | ) 16 | 17 | //MockServerError for some error handler 18 | func MockServerError(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 19 | w.WriteHeader(500) 20 | result := map[string]interface{}{ 21 | "odata.error": map[string]interface{}{ 22 | "error-code": "P129S00003", 23 | "path": nil, 24 | "targetLabel": nil, 25 | "message": map[string]interface{}{ 26 | "lang": "zh-CN", 27 | "value": "系统出错。我们已经收到错误通知,正在处理。", 28 | }, 29 | }, 30 | } 31 | log.Printf("MockServerError Rsp: %+v", result) 32 | 33 | if err := json.NewEncoder(w).Encode(result); err != nil { 34 | panic(err) 35 | } 36 | } 37 | 38 | //NotFoundHandler not found error 39 | func NotFoundHandler(w http.ResponseWriter, r *http.Request) { 40 | w.WriteHeader(200) 41 | r.ParseForm() //解析参数,默认是不会解析的 42 | 43 | log.Println(net.ParseIP(strings.Split(r.RemoteAddr, ":")[0])) 44 | dec := json.NewDecoder(r.Body) 45 | var result interface{} 46 | dec.Decode(&result) 47 | log.Println("Req:", result) 48 | //w.Write([]byte("

Welcome to compasses/MockXServer

")) 49 | w.WriteHeader(200) 50 | w.Write([]byte("[]")) 51 | } 52 | 53 | //BackupDB backupdb 54 | func BackupDB(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { 55 | err := GlobalDB.View(func(tx *bolt.Tx) error { 56 | w.Header().Set("Content-Type", "application/octet-stream") 57 | w.Header().Set("Content-Disposition", `attachment; filename="ReplayDB"`) 58 | w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) 59 | _, err := tx.WriteTo(w) 60 | return err 61 | }) 62 | 63 | if err != nil { 64 | http.Error(w, err.Error(), http.StatusInternalServerError) 65 | } 66 | } 67 | 68 | //PlaceOrder go order 69 | func PlaceOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 70 | r.ParseForm() 71 | dec := json.NewDecoder(r.Body) 72 | var result CheckoutCartPlayLoad 73 | dec.Decode(&result) 74 | log.Printf("got place order request: %+v\n", result) 75 | newOrder := RepoCreateOrder(result) 76 | 77 | log.Println("PlaceOrder Rsp: ", newOrder) 78 | 79 | if err := json.NewEncoder(w).Encode(newOrder); err != nil { 80 | panic(err) 81 | } 82 | } 83 | 84 | //GetSalesOrder return sales order 85 | func GetSalesOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 86 | r.ParseForm() 87 | 88 | dec := json.NewDecoder(r.Body) 89 | var req interface{} 90 | dec.Decode(&req) 91 | Req := req.(map[string]interface{}) 92 | Id := TableId(ToInt64FromString(Req["orderId"].(string))) 93 | 94 | log.Println("GetSalesOrder", req) 95 | salesOrder := RepoGetSalesOrder(Id, Req["channelAccountId"].(string)) 96 | 97 | if err := json.NewEncoder(w).Encode(salesOrder); err != nil { 98 | panic(err) 99 | } 100 | } 101 | 102 | func GetSalesOrders(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 103 | r.ParseForm() 104 | 105 | dec := json.NewDecoder(r.Body) 106 | var req interface{} 107 | dec.Decode(&req) 108 | Req := req.(map[string]interface{}) 109 | 110 | log.Println("GetSalesOrders", req) 111 | salesOrders := RepoGetSalesOrders(Req["channelAccountId"].(string)) 112 | 113 | if err := json.NewEncoder(w).Encode(salesOrders); err != nil { 114 | panic(err) 115 | } 116 | log.Println("GetSalesOrders Rsp", req) 117 | 118 | } 119 | 120 | func Checkout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 121 | r.ParseForm() 122 | dec := json.NewDecoder(r.Body) 123 | var result CheckoutCartPlayLoad 124 | err := dec.Decode(&result) 125 | if err != nil { 126 | HandleError(err) 127 | } 128 | log.Println("CheckoutShoppingCart req: ", result) 129 | 130 | resp := RepoCheckoutShoppingCart(result) 131 | log.Println("CheckoutShoppingCart resp: ", resp) 132 | 133 | if err := json.NewEncoder(w).Encode(resp); err != nil { 134 | panic(err) 135 | } 136 | 137 | } 138 | 139 | //ATS find product ATS 140 | func ATS(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 141 | r.ParseForm() 142 | dec := json.NewDecoder(r.Body) 143 | var checkInfo ATSReq 144 | err := dec.Decode(&checkInfo) 145 | 146 | var rsp interface{} 147 | log.Println("ATS request: ", checkInfo) 148 | 149 | if err != nil { 150 | HandleError(err) 151 | //w.WriteHeader(http.StatusBadRequest) 152 | log.Println("ATS just returen the default") 153 | rsp = map[string]interface{}{ 154 | "allowBackOrder": true, 155 | } 156 | } else { 157 | log.Printf("ATS Req %+v\n", checkInfo) 158 | rsp = RepoCreateATSRsp(&checkInfo) 159 | } 160 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 161 | w.WriteHeader(http.StatusOK) 162 | 163 | if err := json.NewEncoder(w).Encode(rsp); err != nil { 164 | panic(err) 165 | } 166 | } 167 | 168 | func RecommandationProducts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 169 | r.ParseForm() 170 | dec := json.NewDecoder(r.Body) 171 | var id RecommandInfo 172 | err := dec.Decode(&id) 173 | 174 | if err != nil { 175 | HandleError(err) 176 | w.WriteHeader(http.StatusBadRequest) 177 | return 178 | } 179 | log.Printf("RecommandProducts Req%+v\n", id) 180 | 181 | RecommandIds := RepoCreateRecommandationProducts(id.ProductId) 182 | w.Header().Set("Content-Type", "application/json; charset=UTF-8") 183 | 184 | w.WriteHeader(http.StatusOK) 185 | 186 | if err := json.NewEncoder(w).Encode(RecommandIds); err != nil { 187 | panic(err) 188 | } 189 | log.Printf("RecommandProducts Rsp%+v\n", RecommandIds) 190 | } 191 | 192 | func GetCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 193 | r.ParseForm() 194 | var channelAccountId interface{} 195 | dec := json.NewDecoder(r.Body) 196 | 197 | err := dec.Decode(&channelAccountId) 198 | id := channelAccountId.(map[string]interface{}) 199 | 200 | if err != nil { 201 | HandleError(err) 202 | w.WriteHeader(http.StatusBadRequest) 203 | return 204 | } 205 | log.Printf("Get customer req %+v\n", channelAccountId) 206 | 207 | Account := RepoGetCustomer(GetIdFromStr(id["channelAccountId"].(string))) 208 | 209 | if err := json.NewEncoder(w).Encode(Account); err != nil { 210 | panic(err) 211 | } 212 | log.Printf("Return Customer Rsp %+v\n", Account) 213 | } 214 | 215 | func UpdateCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 216 | 217 | if err := json.NewEncoder(w).Encode(nil); err != nil { 218 | panic(err) 219 | } 220 | 221 | log.Printf("customer exist") 222 | } 223 | func CheckEmailExistence(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 224 | 225 | if err := json.NewEncoder(w).Encode(nil); err != nil { 226 | panic(err) 227 | } 228 | log.Printf("customer exist") 229 | } 230 | 231 | func CreateCustomer(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 232 | r.ParseForm() 233 | var customer CustomerCreate 234 | dec := json.NewDecoder(r.Body) 235 | 236 | err := dec.Decode(&customer) 237 | 238 | if err != nil { 239 | HandleError(err) 240 | w.WriteHeader(http.StatusBadRequest) 241 | return 242 | } 243 | 244 | log.Printf("Customer Create Req%+v\n", customer) 245 | 246 | Account := RepoCreateAccount(customer) 247 | 248 | if err := json.NewEncoder(w).Encode(Account); err != nil { 249 | panic(err) 250 | } 251 | log.Printf("Customer Create Rsp%+v\n", Account) 252 | } 253 | 254 | func CustomerAddressNew(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 255 | r.ParseForm() 256 | dec := json.NewDecoder(r.Body) 257 | 258 | var addInfo CustomerAddress 259 | err := dec.Decode(&addInfo) 260 | 261 | if err != nil { 262 | HandleError(err) 263 | w.WriteHeader(http.StatusBadRequest) 264 | return 265 | } 266 | 267 | Rs := RepoCreateAddress(&addInfo) 268 | 269 | if err = json.NewEncoder(w).Encode(Rs); err != nil { 270 | panic(err) 271 | } 272 | 273 | log.Printf("Create address info %+v\n", addInfo) 274 | log.Printf("Result %+v\n", Rs) 275 | 276 | } 277 | 278 | func CustomerAddressUpdate(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { 279 | r.ParseForm() 280 | addressId := GetIdFromStr(ps.ByName("id")) 281 | 282 | dec := json.NewDecoder(r.Body) 283 | 284 | var addInfo CustomerAddress 285 | err := dec.Decode(&addInfo) 286 | 287 | if err != nil { 288 | HandleError(err) 289 | w.WriteHeader(http.StatusBadRequest) 290 | return 291 | } 292 | 293 | Rs := RepoUpdateAddress(addressId, &addInfo) 294 | log.Printf("Update address info %+v\n", Rs) 295 | 296 | if err = json.NewEncoder(w).Encode(Rs); err != nil { 297 | panic(err) 298 | } 299 | } 300 | 301 | func GetCustomerAddress(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 302 | r.ParseForm() 303 | 304 | customerId := GetIdFromStr(r.Form["$filter"][0]) 305 | Rs := RepoGetCustomerAddress(customerId) 306 | 307 | if err := json.NewEncoder(w).Encode(Rs); err != nil { 308 | panic(err) 309 | } 310 | } 311 | func MiscCheck(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 312 | r.ParseForm() 313 | dec := json.NewDecoder(r.Body) 314 | 315 | var checkParam map[string]interface{} 316 | err := dec.Decode(&checkParam) 317 | 318 | if err != nil { 319 | HandleError(err) 320 | w.WriteHeader(http.StatusBadRequest) 321 | return 322 | } 323 | 324 | log.Println("misc check parames ", checkParam) 325 | Rs := RetrieveByMapLevel(checkParam, []string{"lines"}) 326 | lines := Rs.([]interface{}) 327 | 328 | resp := make(map[string][]interface{}) 329 | for _, val := range lines { 330 | valm := val.(map[string]interface{}) 331 | resp["lineResult"] = append(resp["lineResult"], map[string]interface{}{ 332 | "onChannel": "true", 333 | "ats": 10, 334 | "allowBackOrder": "true", 335 | "skuId": valm["skuId"], 336 | "valid": "true", 337 | }) 338 | } 339 | 340 | log.Println("resp ", resp) 341 | 342 | if err = json.NewEncoder(w).Encode(resp); err != nil { 343 | panic(err) 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /offline/sample_routes_define.go: -------------------------------------------------------------------------------- 1 | package offline 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "github.com/julienschmidt/httprouter" 6 | ) 7 | 8 | type Route struct { 9 | Name string 10 | Method string 11 | Pattern string 12 | HandleFunc httprouter.Handle 13 | } 14 | 15 | type Routes []Route 16 | 17 | var routes = Routes{ 18 | Route{ 19 | "ATS Check", 20 | "POST", 21 | "/api/EshopAdapter/Product/v1/getATS", 22 | ATS, 23 | }, 24 | Route{ 25 | "Recommendation Products", 26 | "POST", 27 | "/api/EshopAdapter/Product/v1/getRecommendationProductIds", 28 | RecommandationProducts, 29 | }, 30 | Route{ 31 | "Create Customer", 32 | "POST", 33 | "/api/EshopAdapter/Customer/v1/", 34 | CreateCustomer, 35 | }, 36 | Route{ 37 | "check email exist", 38 | "POST", 39 | "/sbo/service/CustomerService@isEmailAccountExist", 40 | CheckEmailExistence, 41 | }, 42 | Route{ 43 | "Get Customer", 44 | "POST", 45 | "/sbo/service/CustomerService@getCustomer", 46 | GetCustomer, 47 | }, 48 | Route{ 49 | "Address New", 50 | "POST", 51 | "/sbo/service/CustomerAddressNew", 52 | CustomerAddressNew, 53 | }, 54 | Route{ 55 | "Update Customer", 56 | "POST", 57 | "/sbo/service/ EShopService@updateCustomer", 58 | UpdateCustomer, 59 | }, 60 | Route{ 61 | "Address Update", 62 | "PUT", 63 | "/sbo/service/:id/", 64 | CustomerAddressUpdate, 65 | }, 66 | Route{ 67 | "Address Retrieve", 68 | "GET", 69 | "/sbo/service/CustomerAddressNew/", 70 | GetCustomerAddress, 71 | }, 72 | Route{ 73 | "MiscCheck", 74 | "POST", 75 | "/api/EshopAdapter/Product/v1/miscCheck", 76 | MiscCheck, 77 | }, 78 | Route{ 79 | "Checkout", 80 | "POST", 81 | "/api/EshopAdapter/Order/v1/checkoutShoppingCart", 82 | Checkout, 83 | }, 84 | Route{ 85 | "PlaceOrder", 86 | "POST", 87 | "/api/EshopAdapter/Order/v1/placeOrder", 88 | PlaceOrder, 89 | }, 90 | Route{ 91 | "GetSalesOrder", 92 | "POST", 93 | "/sbo/service/EShopService@getSalesOrder", 94 | GetSalesOrder, 95 | }, 96 | Route{ 97 | "GetSalesOrders", 98 | "POST", 99 | "/sbo/service/EShopService@getSalesOrders", 100 | GetSalesOrders, 101 | }, 102 | Route{ 103 | "BackUpDatabase", 104 | "GET", 105 | "/backupDB", 106 | BackupDB, 107 | }, 108 | // Route{ 109 | // "GeneratePACT", 110 | // "GET", 111 | // "/pact", 112 | // GeneratePACT, 113 | // }, 114 | // Route{ 115 | // "GenerateJSON", 116 | // "GET", 117 | // "/json", 118 | // GenerateJSON, 119 | // }, 120 | } 121 | 122 | func NewServerRouter(db *bolt.DB) *httprouter.Router { 123 | router := httprouter.New() 124 | 125 | for _, route := range routes { 126 | httpHandle := Logger(route.HandleFunc, route.Name) 127 | 128 | router.Handle( 129 | route.Method, 130 | route.Pattern, 131 | httpHandle, 132 | ) 133 | } 134 | 135 | router.NotFound = LoggerNotFound(NotFoundHandler) 136 | GlobalDB = db 137 | 138 | return router 139 | } 140 | -------------------------------------------------------------------------------- /online/proxyHandler.go: -------------------------------------------------------------------------------- 1 | package online 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "crypto/tls" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "time" 13 | 14 | "github.com/compasses/MockXServer/db" 15 | . "github.com/compasses/MockXServer/utils" 16 | ) 17 | 18 | type ProxyRoute struct { 19 | client *http.Client 20 | url string 21 | db *db.ReplayDB 22 | } 23 | 24 | func NewProxyHandler(newurl string, db *db.ReplayDB) *ProxyRoute { 25 | tr := &http.Transport{ 26 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 27 | DisableCompression: true, 28 | Dial: (&net.Dialer{ 29 | Timeout: 30 * time.Second, 30 | KeepAlive: 30 * time.Second, 31 | }).Dial, 32 | TLSHandshakeTimeout: 10 * time.Second, 33 | } 34 | 35 | return &ProxyRoute{ 36 | client: &http.Client{Transport: tr}, 37 | url: newurl, 38 | db: db} 39 | } 40 | 41 | func (proxy *ProxyRoute) doReq(NeedLog bool, path, method, requestBody string, newRq *http.Request) (resp *http.Response, res []byte) { 42 | now := time.Now() 43 | resp, err := proxy.client.Do(newRq) 44 | if resp != nil { 45 | defer resp.Body.Close() 46 | } 47 | 48 | LogOutPut(NeedLog, "Time used: ", time.Since(now)) 49 | if err != nil { 50 | log.Println("get error ", err) 51 | } else { 52 | if resp.Header.Get("Content-Encoding") == "gzip" { 53 | resp.Body, err = gzip.NewReader(resp.Body) 54 | if err != nil { 55 | panic(err) 56 | } 57 | } 58 | 59 | res, err = ioutil.ReadAll(resp.Body) 60 | if err != nil { 61 | log.Println("ioutil read err ", err) 62 | } 63 | 64 | if NeedLog { 65 | if resp.StatusCode == 500 || resp.StatusCode == 404 { 66 | FailNum++ 67 | } else { 68 | SuccNum++ 69 | err = proxy.db.StoreRequest(path, method, requestBody, string(res), resp.StatusCode) 70 | } 71 | } 72 | 73 | LogOutPut(NeedLog, "Get response : ") 74 | ResponseFormat(NeedLog, resp, string(res)) 75 | 76 | if err != nil { 77 | log.Println("Store data failed ", err) 78 | } 79 | } 80 | return 81 | } 82 | 83 | func (proxy *ProxyRoute) ServeHTTP(w http.ResponseWriter, req *http.Request) { 84 | newbody, err := ioutil.ReadAll(req.Body) 85 | 86 | if err != nil { 87 | log.Println("Read request failed..", err) 88 | return 89 | } 90 | 91 | newRq, err := http.NewRequest(req.Method, proxy.url+req.RequestURI, ioutil.NopCloser(bytes.NewReader(newbody))) 92 | if err != nil { 93 | log.Println("new request error ", err) 94 | } 95 | 96 | newRq.Header = req.Header 97 | 98 | LogOutPut(true, "online handle, New Request: ") 99 | RequstFormat(true, newRq, string(newbody)) 100 | resphttp, res := proxy.doReq(true, req.RequestURI, req.Method, string(newbody), newRq) 101 | for key, _ := range resphttp.Header { 102 | w.Header().Set(key, strings.Join(resphttp.Header[key], ";")) 103 | } 104 | w.WriteHeader(resphttp.StatusCode) 105 | w.Write(res) 106 | 107 | } 108 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "log" 8 | "net/http" 9 | "reflect" 10 | "regexp" 11 | "runtime" 12 | "strconv" 13 | ) 14 | 15 | type TableId int64 16 | type IDSeqs []TableId 17 | type StrSeqs []string 18 | 19 | //used to avoid recurision 20 | type idseqs IDSeqs 21 | 22 | var SuccNum int = 0 23 | var FailNum int = 0 24 | 25 | func (ids *IDSeqs) UnmarshalJSON(b []byte) (err error) { 26 | log.Println("IDSeqs got bytes: ", string(b)) 27 | var tids *idseqs 28 | if err = json.Unmarshal(b, &tids); err == nil { 29 | *ids = IDSeqs(*tids) 30 | return 31 | } 32 | 33 | strid := new(StrSeqs) 34 | if err = json.Unmarshal(b, strid); err == nil { 35 | for _, val := range *strid { 36 | if v, err := strconv.ParseInt(val, 10, 64); err == nil { 37 | *ids = append(*ids, TableId(v)) 38 | } 39 | } 40 | return 41 | } else { 42 | log.Println("got error: ", err) 43 | } 44 | return 45 | } 46 | 47 | func (id *TableId) UnmarshalJSON(b []byte) (err error) { 48 | log.Println("TableId got bytes: ", string(b)) 49 | var tabd int64 50 | if err = json.Unmarshal(b, &tabd); err == nil { 51 | *id = TableId(tabd) 52 | return 53 | } 54 | 55 | s := "" 56 | if err = json.Unmarshal(b, &s); err == nil { 57 | v, err := strconv.ParseInt(s, 10, 64) 58 | if err == nil { 59 | *id = TableId(v) 60 | } 61 | } 62 | 63 | return 64 | } 65 | 66 | func GetAddressObj(addr interface{}) map[string]interface{} { 67 | old := addr.(map[string]interface{}) 68 | 69 | var result = map[string]interface{}{ 70 | "recipientName": old["userName"], 71 | "cityName": old["customCity"], 72 | "stateId": old["state"], 73 | "countryId": old["country"], 74 | "street1": old["address1"], 75 | "street2": old["address2"], 76 | "zipCode": old["zipCode"], 77 | "mobile": old["phone"], 78 | "state": old["customState"], 79 | } 80 | 81 | return result 82 | } 83 | 84 | func ToInt64FromString(input string) int64 { 85 | re, _ := strconv.ParseInt(input, 10, 8) 86 | return re 87 | } 88 | 89 | func ToFloat64FromString(input string) float64 { 90 | re, _ := strconv.ParseFloat(input, 8) 91 | return re 92 | } 93 | 94 | func GetSliceIntFromBytes(input []byte) []TableId { 95 | sizeofInt := 8 96 | data := make([]TableId, len(input)/sizeofInt) 97 | buf := bytes.NewBuffer(input) 98 | for i := range data { 99 | var re int64 100 | binary.Read(buf, binary.LittleEndian, &re) 101 | data[i] = TableId(re) 102 | } 103 | 104 | return data 105 | } 106 | 107 | func GetSliceBytesFromInts(input []TableId) []byte { 108 | buf := new(bytes.Buffer) 109 | 110 | for i := range input { 111 | binary.Write(buf, binary.LittleEndian, int64(input[i])) 112 | } 113 | return buf.Bytes() 114 | } 115 | 116 | func ContainsIntSlice(s []TableId, e TableId) bool { 117 | for _, a := range s { 118 | if a == e { 119 | return true 120 | } 121 | } 122 | return false 123 | } 124 | 125 | func (tId TableId) ToBytes() []byte { 126 | buf := new(bytes.Buffer) 127 | binary.Write(buf, binary.LittleEndian, int64(tId)) 128 | return buf.Bytes() 129 | } 130 | 131 | func (tId TableId) ToString() (str string) { 132 | str = strconv.FormatInt(int64(tId), 10) 133 | return 134 | } 135 | 136 | func (tId TableId) ToInt() int64 { 137 | return int64(tId) 138 | } 139 | 140 | func ToInt64FromBytes(st []byte) int64 { 141 | buf := bytes.NewReader(st) 142 | var result int64 143 | binary.Read(buf, binary.LittleEndian, &result) 144 | return result 145 | } 146 | 147 | //proc string like "createcustomernew(1)", and return 1 148 | func GetIdFromStr(input string) TableId { 149 | valId := regexp.MustCompile(`(\d+)`) 150 | val, _ := strconv.Atoi(valId.FindString(input)) 151 | return TableId(val) 152 | } 153 | 154 | func RetrieveByMapLevel(f interface{}, levels []string) interface{} { 155 | length := len(levels) 156 | 157 | if length <= 0 { 158 | return f 159 | } 160 | 161 | result := f.(map[string]interface{}) 162 | 163 | for k, v := range result { 164 | if k == levels[0] { 165 | return RetrieveByMapLevel(v.(interface{}), levels[1:]) 166 | } 167 | } 168 | 169 | return result 170 | } 171 | 172 | func HandleDecodeData(f map[string]interface{}) { 173 | for k, v := range f { 174 | log.Println("key", k) 175 | switch vv := v.(type) { 176 | // case string: 177 | // log.Println(k, "is string", vv) 178 | // case int: 179 | // log.Println(k, "is int", vv) 180 | // case []interface{}: 181 | // log.Println(k, "is an array:") 182 | // for i, u := range vv { 183 | // log.Println(i, u) 184 | // } 185 | // case interface{}: 186 | // log.Println(k, "is an interface") 187 | // HandleDecodeData(f[k].(map[string]interface{})) 188 | default: 189 | log.Println(vv) 190 | log.Println(k, "is of a type I don't know how to handle") 191 | } 192 | } 193 | } 194 | 195 | func HandleError(err error) { 196 | _, file, line, ok := runtime.Caller(1) 197 | if !ok { 198 | file = "???" 199 | line = 0 200 | } 201 | log.Println(err, file, "line:", line) 202 | } 203 | 204 | func LogOutPut(NeedLog bool, v ...interface{}) { 205 | if NeedLog { 206 | log.Println(v) 207 | } 208 | } 209 | 210 | func RequstFormat(NeedLog bool, req *http.Request, newbody string) { 211 | if !NeedLog { 212 | return 213 | } 214 | result := "URL: " + req.URL.String() + "\r\n" 215 | result += "Method: " + req.Method + "\r\n" 216 | result += "Body: " + newbody + "\r\n" 217 | result += "Header: " 218 | for key, _ := range req.Header { 219 | var vals string = "" 220 | for _, allV := range req.Header[key] { 221 | vals += allV 222 | } 223 | result += " Key: " + key + " -> " + vals + "\r\n" 224 | } 225 | LogOutPut(NeedLog, result) 226 | } 227 | 228 | func ResponseFormat(NeedLog bool, resp *http.Response, body string) { 229 | if !NeedLog { 230 | return 231 | } 232 | 233 | result := "Status: " + resp.Status + "\r\n" 234 | result += "Body: " + body + "\r\n" 235 | result += "Header: " 236 | for key, _ := range resp.Header { 237 | var vals string = "" 238 | for _, allV := range resp.Header[key] { 239 | vals += allV 240 | } 241 | result += " Key: " + key + " -> " + vals + "\r\n" 242 | } 243 | LogOutPut(NeedLog, result) 244 | } 245 | 246 | func ReflectStruct(req *http.Request) { 247 | s := reflect.ValueOf(req).Elem() 248 | typeOfT := s.Type() 249 | for i := 0; i < s.NumField(); i++ { 250 | f := s.Field(i) 251 | log.Printf("%d: %s %s = %v\n", i, 252 | typeOfT.Field(i).Name, f.Type(), f.Interface()) 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /utils/helper_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "crypto/tls" 6 | "database/sql" 7 | "encoding/binary" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | "os" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "testing" 18 | 19 | "github.com/franela/goreq" 20 | 21 | "golang.org/x/text/encoding" 22 | "golang.org/x/text/encoding/simplifiedchinese" 23 | "golang.org/x/text/transform" 24 | ) 25 | 26 | func TestBytesToInt(t *testing.T) { 27 | test1 := int64(256) 28 | buf := new(bytes.Buffer) 29 | binary.Write(buf, binary.LittleEndian, test1) 30 | //binary.PutVarint(buf.Bytes(), int64(test1)) 31 | fmt.Println("byte is ", buf.Bytes()) 32 | bufr := bytes.NewBuffer(buf.Bytes()) 33 | var rest int64 34 | err := binary.Read(bufr, binary.LittleEndian, &rest) 35 | if err != nil { 36 | t.Error(err) 37 | } 38 | fmt.Println("rest value ", rest) 39 | 40 | num, _ := binary.Varint(buf.Bytes()) 41 | fmt.Println("num is ", num) 42 | 43 | var testS []TableId 44 | testS = append(testS, 34, 3443, 344242) 45 | tids := GetSliceBytesFromInts(testS) 46 | fmt.Println("length is ", len(tids), "val is ", tids) 47 | 48 | tidr := GetSliceIntFromBytes(tids) 49 | fmt.Println("int value is ", tidr) 50 | 51 | t4 := ToInt64FromBytes(tids[0:8]) 52 | fmt.Println("t4 val is ", t4) 53 | 54 | } 55 | 56 | func TestBytesToInt2(t *testing.T) { 57 | var intVar = 123 58 | fmt.Println("intVar is : ", intVar) 59 | 60 | intByte := []byte(strconv.Itoa(intVar)) 61 | 62 | fmt.Println("intByte is : ", intByte) 63 | } 64 | 65 | func TestGetIntFromStr(t *testing.T) { 66 | input := "CreateCustomerNew(23)" 67 | valId := regexp.MustCompile(`(\d+)`) 68 | val, _ := strconv.Atoi(valId.FindString(input)) 69 | fmt.Println("id :", val) 70 | var ni []interface{} 71 | ni = append(ni, "ok") 72 | ni = append(ni, 2323) 73 | 74 | fmt.Println("interfaces values ", ni) 75 | } 76 | 77 | func TestHttpGet(t *testing.T) { 78 | res, err := goreq.Request{ 79 | Method: "GET", 80 | Uri: "https://cnpvgvb1ep052.pvgl.sap.corp:29900", 81 | ShowDebug: true, 82 | }.Do() 83 | 84 | // dec := json.NewDecoder(res.Response.Body) 85 | // var result interface{} 86 | // dec.Decode(&result) 87 | 88 | // //fmt.Println("Result ", res.Response) 89 | // fmt.Println("resp result ", result) 90 | fmt.Println("error is ", err) 91 | if err == nil { 92 | nres1, _ := ioutil.ReadAll(res.Response.Body) 93 | fmt.Println("raw body is ", string(nres1)) 94 | } 95 | 96 | tr := &http.Transport{ 97 | TLSClientConfig: &tls.Config{}, 98 | DisableCompression: true, 99 | } 100 | client := &http.Client{Transport: tr} 101 | resp, err := client.Get("https://www.baidu.com") 102 | fmt.Println("err is ", err) 103 | defer resp.Body.Close() 104 | dec := json.NewDecoder(resp.Body) 105 | var result2 interface{} 106 | err = dec.Decode(&result2) 107 | fmt.Println("decode error ", err) 108 | fmt.Println("body is ", result2) 109 | nres, _ := ioutil.ReadAll(resp.Body) 110 | fmt.Println("raw body is ", string(nres)) 111 | 112 | } 113 | 114 | func TestDBCase(t *testing.T) { 115 | db, err := sql.Open("mysql", "root:12345@tcp(cnpvgvb1ep140.pvgl.sap.corp:3306)/") 116 | if err != nil { 117 | panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic 118 | } 119 | defer db.Close() 120 | 121 | // Open doesn't open a connection. Validate DSN data: 122 | err = db.Ping() 123 | if err != nil { 124 | panic(err.Error()) // proper error handling instead of panic in your app 125 | } 126 | 127 | // Select the DataBase 128 | db.Exec("use ESHOPDB116") 129 | // Execute the query 130 | // rows, err := db.Query("show databases") 131 | // if err != nil { 132 | // panic(err.Error()) // proper error handling instead of panic in your app 133 | // } 134 | rows, err := db.Query("select option_value from wp_options where option_name = 'eshopSetting'") 135 | // Get column names 136 | // columns, err := rows.Columns() 137 | // fmt.Println("columns: ", columns) 138 | 139 | if err != nil { 140 | panic(err.Error()) // proper error handling instead of panic in your app 141 | } 142 | 143 | // // Make a slice for the values 144 | // values := make([]sql.RawBytes, 1) 145 | 146 | // // rows.Scan wants '[]interface{}' as an argument, so we must copy the 147 | // // references into such a slice 148 | // // See http://code.google.com/p/go-wiki/wiki/InterfaceSlice for details 149 | // scanArgs := make([]interface{}, len(values)) 150 | // for i := range values { 151 | // scanArgs[i] = &values[i] 152 | // } 153 | // fmt.Printf("scanArgs: %v\n", values) 154 | var res []byte 155 | // Fetch rows 156 | for rows.Next() { 157 | // get RawBytes from data 158 | err = rows.Scan(&res) 159 | 160 | var dataS interface{} 161 | json.Unmarshal(res, &dataS) 162 | m := dataS.(map[string]interface{}) 163 | fmt.Println(": ", m["shopName"]) 164 | if err != nil { 165 | panic(err.Error()) // proper error handling instead of panic in your app 166 | } 167 | 168 | // Now do something with the data. 169 | // // Here we just print each column as a string. 170 | // var value string 171 | // for _, col := range values { 172 | // // Here we can check if the value is nil (NULL value) 173 | // if col == nil { 174 | // value = "NULL" 175 | // } else { 176 | // value = string(col) 177 | // } 178 | 179 | // } 180 | // fmt.Println("-----------------------------------") 181 | } 182 | if err = rows.Err(); err != nil { 183 | panic(err.Error()) // proper error handling instead of panic in your app 184 | } 185 | } 186 | 187 | func load(direction string, enc encoding.Encoding) (func() transform.Transformer, error) { 188 | 189 | newTransformer := enc.NewEncoder 190 | if direction == "Decode" { 191 | newTransformer = enc.NewDecoder 192 | } 193 | 194 | return newTransformer, nil 195 | } 196 | 197 | func TestConversion(t *testing.T) { 198 | f, err := os.OpenFile("testlogfile", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 199 | if err != nil { 200 | t.Fatalf("error opening file: %v", err) 201 | } 202 | defer f.Close() 203 | 204 | log.SetOutput(f) 205 | log.Println("This is a test log entry") 206 | 207 | sr := strings.NewReader("你好,世界") 208 | 209 | newTransformer, _ := load("Decode", simplifiedchinese.GBK) 210 | 211 | rInUTF8 := transform.NewReader(sr, newTransformer()) 212 | res := make([]byte, 100) 213 | rInUTF8.Read(res) 214 | 215 | log.Println("t is ", "真的吗", string(res)) 216 | } 217 | -------------------------------------------------------------------------------- /utils/jsonrelated.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func TOJsonInterface(body []byte) (result interface{}, err error) { 12 | err = json.Unmarshal(body, &result) 13 | if err != nil { 14 | log.Println("Unmarshal failed ", err, body) 15 | } 16 | return 17 | } 18 | 19 | func JsonInterfaceToByte(body interface{}) (result []byte) { 20 | var err error 21 | result, err = json.Marshal(body) 22 | if err != nil { 23 | log.Println("Marshal failed ", err, body) 24 | } 25 | return 26 | } 27 | 28 | func JsonInterfaceToByteByNumber(body []byte) (result []byte) { 29 | d := json.NewDecoder(strings.NewReader(string(body))) 30 | d.UseNumber() 31 | var x interface{} 32 | if err := d.Decode(&x); err != nil { 33 | log.Fatal(err) 34 | } 35 | fmt.Printf("decoded to %#v\n", x) 36 | result, err := json.Marshal(x) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | return 41 | } 42 | 43 | func JsonNormalizeSingle(body string) (stream []byte, err error) { 44 | streamMap, err := TOJsonInterface([]byte(body)) 45 | stream, err = json.Marshal(streamMap) 46 | if err != nil { 47 | log.Println("Marshal failed ", err, streamMap) 48 | } 49 | return 50 | } 51 | 52 | func JsonNormalize(reqBody, respBody string, statusCode int) (reqStram, respStram []byte, err error) { 53 | // reqMap, err := TOJsonInterface([]byte(reqBody)) 54 | // respMap, err := TOJsonInterface([]byte(respBody)) 55 | 56 | rspStream := map[string]string{ 57 | strconv.Itoa(statusCode): respBody, 58 | } 59 | 60 | respStram, err = json.Marshal(rspStream) 61 | if err != nil { 62 | log.Println("Marshal failed ", err, rspStream) 63 | } 64 | 65 | reqStram = []byte(reqBody) 66 | // 67 | // reqStram, err = json.Marshal(reqMap) 68 | // if err != nil { 69 | // log.Println("Marshal failed ", err, reqMap) 70 | // } 71 | 72 | return 73 | } 74 | -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | Version = "2016-06-22 19:52:35 +0800 @8224127" 5 | Compile = "2016-06-24 14:27:22 +0800 by go version go1.6.2 darwin/amd64" 6 | ) 7 | -------------------------------------------------------------------------------- /workflow.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/compasses/MockXServer/1169543ae7d372309fc9dbbdf9aaba32a7b95c91/workflow.PNG --------------------------------------------------------------------------------