Sort payload by request/reply pairs

This removes the need for "jump to reply" buttons, which were not user
friendly. Also display the ContentType in the payload chunk heading
This commit is contained in:
emanuele-f 2022-04-27 14:45:39 +02:00
parent ede31c61d7
commit 6ffcdd41da
6 changed files with 63 additions and 70 deletions

View File

@ -41,6 +41,7 @@ public class HTTPReassembly {
private boolean mReadingHeaders;
private boolean mChunkedEncoding;
private ContentEncoding mContentEncoding;
private String mContentType;
private int mContentLength;
private int mHeadersSize;
private final ArrayList<PayloadChunk> mHeaders = new ArrayList<>();
@ -68,6 +69,7 @@ public class HTTPReassembly {
mContentEncoding = ContentEncoding.UNKNOWN;
mChunkedEncoding = false;
mContentLength = -1;
mContentType = null;
mHeadersSize = 0;
mHeaders.clear();
mBody.clear();
@ -126,8 +128,10 @@ public class HTTPReassembly {
break;
}
} else if(line.startsWith("content-type: ")) {
String contentType = line.substring(14);
log_d("Content-Type: " + contentType);
int endIdx = line.indexOf(";");
mContentType = line.substring(14, (endIdx > 0) ? endIdx : line.length());
log_d("Content-Type: " + mContentType);
} else if(line.startsWith("content-length: ")) {
try {
mContentLength = Integer.parseInt(line.substring(16));
@ -253,6 +257,7 @@ public class HTTPReassembly {
if(mInvalidHttp)
to_add.type = PayloadChunk.ChunkType.RAW;
to_add.contentType = mContentType;
mListener.onChunkReassembled(to_add);
reset(); // mReadingHeaders = true
}

View File

@ -29,7 +29,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.emanuelef.remote_capture.CaptureService;
@ -40,7 +39,6 @@ import com.emanuelef.remote_capture.model.ConnectionDescriptor;
import com.emanuelef.remote_capture.model.PayloadChunk;
import com.emanuelef.remote_capture.model.PayloadChunk.ChunkType;
import com.emanuelef.remote_capture.model.Prefs;
import com.google.android.material.button.MaterialButton;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
@ -61,15 +59,13 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
private final Context mContext;
private final ChunkType mMode;
private int mHandledChunks;
private AdapterChunk mUnmatchedHttpReq = null;
private AdapterChunk mUnrepliedHttpReq = null;
private final ArrayList<AdapterChunk> mChunks = new ArrayList<>();
private final LinearLayoutManager mLinearLayout;
private final HTTPReassembly mHttpReq;
private final HTTPReassembly mHttpRes;
public PayloadAdapter(Context context, LinearLayoutManager linearLayout, ConnectionDescriptor conn, ChunkType mode) {
public PayloadAdapter(Context context, ConnectionDescriptor conn, ChunkType mode) {
mLayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLinearLayout = linearLayout;
mConn = conn;
mContext = context;
mMode = mode;
@ -89,12 +85,11 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
private String mTheText;
private boolean mIsExpanded;
private int mNumPages = 1;
public final int originalPos;
public AdapterChunk peer = null;
public final int incrId;
AdapterChunk(PayloadChunk _chunk, int pos) {
AdapterChunk(PayloadChunk _chunk, int incr_id) {
mChunk = _chunk;
originalPos = pos;
incrId = incr_id;
}
boolean canBeExpanded() {
@ -194,8 +189,8 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
View headerLine;
TextView header;
TextView dump;
TextView contentType;
ImageView expandButton;
MaterialButton jumpToReply;
public PayloadViewHolder(View view) {
super(view);
@ -204,7 +199,7 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
header = view.findViewById(R.id.header);
dump = view.findViewById(R.id.dump);
expandButton = view.findViewById(R.id.expand_button);
jumpToReply = view.findViewById(R.id.jumpToReply);
contentType = view.findViewById(R.id.content_type);
}
}
@ -231,16 +226,6 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
}
});
holder.jumpToReply.setOnClickListener(v -> {
int pos = holder.getAbsoluteAdapterPosition();
Page page = getItem(pos);
assert(page.adaptChunk.peer != null);
int jumpPos = getAdapterPosition(page.adaptChunk.peer);
Log.d(TAG, "jump to " + jumpPos + " (orig_pos=" + page.adaptChunk.peer.originalPos + ")");
mLinearLayout.scrollToPositionWithOffset(jumpPos, 0);
});
return holder;
}
@ -261,15 +246,12 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
Locale locale = Utils.getPrimaryLocale(mContext);
holder.header.setText(String.format(locale,
"#%d [%s] %s — %s", page.adaptChunk.originalPos + 1,
"#%d [%s] %s — %s", page.adaptChunk.incrId + 1,
getHeaderTag(chunk),
(new SimpleDateFormat("HH:mm:ss.SSS", locale)).format(new Date(chunk.timestamp)),
Utils.formatBytes(chunk.payload.length)));
holder.jumpToReply.setVisibility(((page.adaptChunk.peer != null) &&
/* Only show the button if request and reply are not consecutive */
(Math.abs(page.adaptChunk.peer.originalPos - page.adaptChunk.originalPos) != 1))
? View.VISIBLE : View.INVISIBLE);
holder.contentType.setText((chunk.contentType != null) ? chunk.contentType : "");
} else
holder.headerLine.setVisibility(View.GONE);
@ -364,31 +346,42 @@ public class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.PayloadV
mHandledChunks = tot_chunks;
}
private void setNextUnrepliedRequest(int prevChunkIdx) {
// Possibly find next un-replied HTTP request
for(int i=prevChunkIdx + 1; i<mChunks.size(); i++) {
AdapterChunk cur = mChunks.get(i);
if(cur.mChunk.is_sent) {
mUnrepliedHttpReq = cur;
return;
}
}
// no unreplied found
mUnrepliedHttpReq = null;
}
@Override
public void onChunkReassembled(PayloadChunk chunk) {
AdapterChunk adapterChunk = new AdapterChunk(chunk, mChunks.size());
int adapterPos = getItemCount();
int insertPos = mChunks.size();
if(!chunk.is_sent && (mUnmatchedHttpReq != null)) {
// HTTP reply
adapterChunk.peer = mUnmatchedHttpReq;
mUnmatchedHttpReq.peer = adapterChunk;
notifyItemChanged(getAdapterPosition(mUnmatchedHttpReq));
mUnmatchedHttpReq = null;
// Need to determine where to add the chunk. If HTTP request, always add it to the bottom.
// If HTTP reply, it should be added right after the first un-replied HTTP request
if(!chunk.is_sent && (mUnrepliedHttpReq != null)) {
// HTTP reply to a matching request
int reqPos = mChunks.indexOf(mUnrepliedHttpReq);
assert(reqPos >= 0);
// Possibly find next un-replied HTTP request
for(int i=adapterChunk.peer.originalPos + 1; i<mChunks.size(); i++) {
AdapterChunk cur = mChunks.get(i);
insertPos = reqPos + 1;
adapterPos = getAdapterPosition(mUnrepliedHttpReq) + mUnrepliedHttpReq.getNumPages();
Log.d(TAG, String.format("chunk #%d reply of #%d at %d", adapterChunk.incrId, mUnrepliedHttpReq.incrId, insertPos));
setNextUnrepliedRequest(reqPos);
} else if((chunk.is_sent) && (mUnrepliedHttpReq == null))
mUnrepliedHttpReq = adapterChunk;
if(cur.mChunk.is_sent) {
mUnmatchedHttpReq = cur;
break;
}
}
} else if((chunk.is_sent) && (mUnmatchedHttpReq == null))
mUnmatchedHttpReq = adapterChunk;
int insert_pos = getItemCount();
mChunks.add(adapterChunk);
notifyItemInserted(insert_pos);
mChunks.add(insertPos, adapterChunk);
notifyItemInserted(adapterPos);
}
}

View File

@ -91,7 +91,7 @@ public class ConnectionPayload extends Fragment implements ConnectionDetailsActi
if(mConn.isPayloadTruncated())
mTruncatedWarning.setVisibility(View.VISIBLE);
mAdapter = new PayloadAdapter(requireContext(), layoutMan, mConn, mode);
mAdapter = new PayloadAdapter(requireContext(), mConn, mode);
mCurChunks = mConn.getNumPayloadChunks();
recyclerView.setAdapter(mAdapter);
}

View File

@ -27,6 +27,7 @@ public class PayloadChunk implements Serializable {
public boolean is_sent;
public long timestamp;
public ChunkType type;
public String contentType;
// Serializable need in ConnectionPayload fragment
public enum ChunkType implements Serializable {

View File

@ -1,5 +0,0 @@
<vector android:height="18dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="18dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M11,9l1.42,1.42L8.83,14H18V4h2v12H8.83l3.59,3.58L11,21l-6,-6 6,-6z"/>
</vector>

View File

@ -17,28 +17,27 @@
<TextView
android:id="@+id/header"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:fontFamily="monospace"
android:textSize="11sp"
android:textStyle="bold"
tools:text="#1 [TX] 11:02:03.154 — 120 B" />
<com.google.android.material.button.MaterialButton
android:id="@+id/jumpToReply"
android:layout_width="wrap_content"
<TextView
android:id="@+id/content_type"
android:layout_width="0dp"
android:layout_weight="1"
android:textAlignment="textEnd"
android:layout_marginStart="16dp"
android:layout_marginEnd="2dp"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:insetTop="0dp"
android:insetBottom="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:paddingVertical="0dp"
android:paddingHorizontal="0dp"
android:padding="4dp"
app:icon="@drawable/ic_subdirectory_arrow_left"
app:iconTint="@color/colorTabText" />
android:fontFamily="monospace"
android:textSize="11sp"
android:textStyle="italic"
android:singleLine="true"
android:ellipsize="start"
tools:text="application/javascript" />
</LinearLayout>
<TextView